<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[NullVoxPopuli]]></title><description><![CDATA[NullVoxPopuli]]></description><link>https://nullvoxpopuli.com/rss.xml</link><generator>ember-casper-template</generator><lastBuildDate>Tue, 09 Jun 2026 14:10:13 GMT</lastBuildDate><atom:link href="https://nullvoxpopuli.com/rss.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[EmberConf 2019]]></title><description><![CDATA[<p>This was my first time giving a 'talk'… first time in Portland, Oregon… and I got to meet so many great people! I already had a very positive view of the community from interacting with various people online, but now that I've met a number of them in person, I cannot express in words just how wonderful / thoughtful / inclusive the ember community is.</p>
<p>During the conference I noticed a lot of neat ways to demonstrate and focus on code on slides. I didn't do well at this during my talk, but wanted to provide a place where I track updates to the slides in case anyone wants to reference them. I know that I'll be referencing them as I try to flesh out some ideas in more details. 30 minutes is not a lot of time with such a broad topic!</p>
<p><a href="https://docs.google.com/presentation/d/1ZkFy4JEG8II7OK_rNLGI8M83jjqWFLkJUmKsr4tSY5I/edit?usp=sharing">Link to slides from the Presentation</a>: <a href="https://docs.google.com/presentation/d/1ZkFy4JEG8II7OK_rNLGI8M83jjqWFLkJUmKsr4tSY5I/edit?usp=sharing">Comparing Patterns in React and Ember</a></p>
<ul>
<li>2019-03-19: Talk / Presented</li>
<li>2019-03-21: Add code focus/highlight boxes and split up presenter notes to match focus/highlight boxes</li>
</ul>
<p>* I've technically given talks at local meetups before.<br />
one was on a ruby gem for bringing the ideas behind ember's module unification efforts to rails, <a href="https://github.com/nullvoxpopuli/drawers">Drawers</a>, and the other was on a topic similar to what I spoke about at emberconf, but much higher level.</p>
<p>** My first <em>conference</em> was gencon 2018. That event takes over the entire downtown Indianapolis area.</p>]]></description><link>https://nullvoxpopuli.com/2019-03-21--emberconf-2019</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-03-21--emberconf-2019</guid><pubDate>Thu, 21 Mar 2019 20:30:13 GMT</pubDate></item><item><title><![CDATA[How this blog was created]]></title><description><![CDATA[<p>The tools behind this blog were demo'd at EmberConf by <a href="https://twitter.com/real_ate">Chris Manson</a> during a 5 minute lightning talk where he went from nothing to deployed in ~5 minutes.</p>
<p>Since I didn't yet have a domain, I figured it would take a bit longer.</p>
<p>Here are the steps I took to create this blog / site:</p>
<ul>
<li><p>buy domain at <a href="https://www.namecheap.com">namecheap.com</a></p></li>
<li><p>create blog:</p>
<ul>
<li><code>ember new website --yarn</code></li>
<li><code>cd website</code></li>
<li><code>ember install empress-blog empress-blog-casper-template</code></li>
<li>create repo on github</li>
<li>push code to github</li></ul>
<pre><code class="bash language-bash">  git remote add origin &lt;url from github&gt;
  git add .
  git commit -m "first commit"
  git push -u origin master</code></pre></li>
<li><p>visit netlify and setup a new netlify app</p>
<ul>
<li><p>connect netlify to the repo I created on github</p></li>
<li><p>tell netlify about the domain I bought on namecheap</p></li>
<li><p>configure namecheap to use different Nameservers so that netlify can manage DNS for my domain.</p></li>
<li><p>wait for those changes to propagate the internet (~20 minutes)</p></li>
<li><p>notice that netlify says that I need to add a redirect from the netlify default domain to my domain</p>
<ul>
<li>I created this file in config/_redirects</li></ul>
<pre><code># Redirect default Netlify subdomain to primary domain
https://nullvoxpopuli-website.netlify.com/* https://nullvoxpopuli.com/:splat 301!</code></pre>
<ul>
<li>add a deploy script to my package.json:</li></ul>
<pre><code class="bash language-bash">ember build -e production &amp;&amp; cp ./config/_redirects dist/</code></pre>
<ul>
<li>update my netlify build command under the deploy settings to use <code>yarn deploy</code></li></ul></li></ul></li>
<li><p>after committing and pushing the _redirects update, I got a build error about one of my dependencies not supporting node 10. I had to create an <code>.nvmrc</code> file that specifies node 8, as I found out during my research that netlify will read the <code>.nvmrc</code> file to determine which node version to use during build and deployment.</p></li>
<li><p>after waiting for the automated deploy to finish after pushing the <code>.nvmrc</code> file, the blog was finally up and running!</p></li>
</ul>
<p>Compared to other blogs I've setup in the past, this was very easy, and most of my time was spent on DNS configuration.</p>]]></description><link>https://nullvoxpopuli.com/2019-03-21-how-i-created-this</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-03-21-how-i-created-this</guid><pubDate>Thu, 21 Mar 2019 17:30:13 GMT</pubDate></item><item><title><![CDATA[A General Project Structure That Works in Any Ecosystem]]></title><description><![CDATA[<p>To quote another article on a similar topic:</p>
<blockquote>
  <p>the ideal structure is the one that allows you to move around your code with the least amount of effort.</p>
</blockquote>
<blockquote>
  <p>--- <a href="https://hackernoon.com/the-100-correct-way-to-structure-a-react-app-or-why-theres-no-such-thing-3ede534ef1ed"><em>The 100% correct way to structure a React app…</em></a></p>
</blockquote>
<p>Why worry about folder/file structure at all? It seems like a difficult problem to solve. When there are no restrictions, almost everyone has a different idea of how 'things' should be named, and where they should live. In order to get everyone on the same page to achieve maximum project consistency, a structure should be agreed upon beforehand.</p>
<p><a href="https://reactjs.org/docs/faq-structure.html">There</a> <a href="https://daveceddia.com/react-project-structure/">are</a> <a href="https://levelup.gitconnected.com/structure-your-react-redux-project-for-scalability-and-maintainability-618ad82e32b7">many</a> <a href="https://survivejs.com/react/advanced-techniques/structuring-react-projects/">topics</a> <a href="https://hackernoon.com/the-100-correct-way-to-structure-a-react-app-or-why-theres-no-such-thing-3ede534ef1ed">on</a> <a href="https://blog.bitsrc.io/structuring-a-react-project-a-definitive-guide-ac9a754df5eb">file</a> <a href="https://medium.com/@alexmngn/how-to-better-organize-your-react-applications-2fd3ea1920f1">structure</a>. <a href="https://medium.com/ottofellercom/how-to-structure-large-react-apps-440b0e012d80">None</a> <a href="https://redux.js.org/faq/codestructure">of</a> <a href="https://labs.mlssoccer.com/a-javascript-project-structure-i-can-finally-live-with-52b778041b72">them</a> <a href="https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure">agree</a>. <a href="https://gist.github.com/tracker1/59f2c13044315f88bee9">Some</a> <a href="https://www.oreilly.com/library/view/maintainable-javascript/9781449328092/ch13.html">may</a> <a href="https://wecodetheweb.com/2015/05/28/how-to-structure-your-front-end-application/">have</a> <a href="https://itnext.io/how-to-structure-a-vue-js-project-29e4ddc1aeeb">some</a> <a href="https://kamranahmed.info/blog/2014/08/07/how-to-structure-your-javascript/">similar</a> <a href="https://css-tricks.com/how-do-you-structure-javascript-the-module-pattern-edition/">concepts</a>. <a href="https://lostechies.com/derickbailey/2012/02/02/javascript-file-folder-structures-just-pick-one/">Some</a> <a href="http://cassandrawilcox.me/setting-up-a-node-js-project-with-npm/">may</a> <a href="https://neutrinojs.org/project-layout.html">be</a> <a href="https://guide.meteor.com/structure.html">too</a> <a href="https://www.sitepoint.com/anatomy-of-a-modern-javascript-application/">relaxed</a> <a href="https://vuejs-templates.github.io/webpack/structure.html">to</a> <a href="https://www.sohamkamani.com/blog/2015/08/21/frontend/">be</a> <a href="https://scotch.io/tutorials/angularjs-best-practices-directory-structure">worth</a><a href="http://read.humanjavascript.com/ch04-organizing-your-code.html">while</a>. <a href="https://engineering.opsgenie.com/how-to-organize-react-files-before-its-messed-up-c85387f691be">Ultimately</a>, <a href="https://www.bignerdranch.com/blog/javascript-project-configuration/">when</a> <a href="https://en.bem.info/methodology/filestructure/">faced</a> <a href="https://www.toptal.com/meteor/improving-project-structure-meteor-framework">with</a> <a href="https://www.reddit.com/r/reactjs/comments/8ogngn/what_is_the_most_efficient_folder_structure_for_a/">the</a> <a href="https://github.com/aurelia/framework/blob/master/doc/article/drafts/Aurelia%20Project%20Structure.md">choice</a> <a href="https://tech.offgrid-electric.com/domain-directory-structure-for-react-apps-why-its-worth-trying-b3855ee77a1e">of</a> <a href="https://www.nylas.com/blog/structuring-a-complex-react-redux-project">where</a> <a href="https://blog.usejournal.com/folder-structure-in-react-apps-c2ae8974d21f">to</a> <a href="https://expertise.jetruby.com/how-to-properly-structure-your-react-applications-5609ad3f2ee6">put</a> <a href="http://react-file-structure.surge.sh/">a</a> <a href="https://geeks.uniplaces.com/how-to-keep-your-ember-js-project-clean-and-well-structured-fbff040274de">file</a>, <a href="https://deaddesk.top/choosing-the-proper-redux-project-structure/">everyone's</a> <a href="https://mdbootstrap.com/angular/angular-project-structure/">preference</a> <a href="http://jamesknelson.com/cruv-react-project-structure/">seems</a> <a href="https://www.c-sharpcorner.com/blogs/folder-structure-of-angular-5-project">to</a> <a href="https://www.academind.com/learn/vue-js/nuxt-js-tutorial-introduction/folders-files/">be</a> <a href="https://medium.freecodecamp.org/feature-u-cf3277b11318">a</a> <a href="https://quasar-framework.org/guide/app-directory-structure.html">little</a> <a href="http://react-file-structure.surge.sh/">different</a>.</p>
<p>So, how is <em>this</em> article going to be any different? My goal is to define a set of criteria for which we can assess a folder/file structure, and then to describe a reasonable start to a structure that can work as a base for any single-page-app in any ecosystem -- React, Vue, Angular, or Ember.</p>
<p>Firstly, let's define the criteria that we'll assess structures with.</p>
<ol>
<li>Users should be able to maintain their apps without worrying about the structure of their imports inhibiting them from making changes.</li>
<li>Related files should be discoverable, such that a user does not need to hunt for a file should they not be using TypeScript (where you'd be able to use "Go to definition")</li>
<li>Related files should be accessible, such that a user can easily locate a related file without having any IDE features (i.e.: browsing on github).</li>
<li>Users should have reasonable context at any level within their project hierarchy. Flattening out too much <em>is</em> overwhelming and reduces the ability to maintain, discover, and access.</li>
<li>Refactoring sections of the project should be easy. When moving a directory to a new location, the internal behavior should remain functional.</li>
<li>The right way and place to add a new thing should be obvious and a structure should not allow for unnecessary decisions.</li>
<li>Tests and styles should be co-located along side components.</li>
<li>Avoid the infamous "titlebar problem", where a bunch of files all named the same can't be differentiated in the editor (though, a lot of this is editor-based)</li>
<li>The structure should not impose limitations that would prevent technical advancement -- such as the addition of <a href="https://survivejs.com/webpack/building/code-splitting/">code-splitting</a> to a project that does not yet have it.</li>
</ol>
<h2 id="the-general-enough-to-work-for-all-apps-layout">The general-enough-to-work-for-all-apps-layout:</h2>
<p>Note that any combination of <code>{folder-name}/component.js,template.hbs</code> should be synonymous with:</p>
<ul>
<li>React: <code>{folder-name}/index.jsx,display.jsx</code></li>
<li>Vue: <code>{folder-name}/index.vue,display.vue</code></li>
<li>Angular: <code>{folder-name}/component.js,template.html</code></li>
<li>Ember: <code>{folder-name}/component.js,template.hbs</code></li>
<li>etc</li>
</ul>
<p>Also, note in these examples are shorthand, and some projects (particularly Angular projects), like to be <em>very</em> explicit with naming, such as <code>ComponentName/ComponentName.Component.js</code>.</p>
<pre><code>src
├── data
├── redux-store
├── ui
│   ├── components
│   │   └── list-paginator
│   │       ├── paginator-control
│   │       │   ├── component.js
│   │       │   └── template.hbs
│   │       ├── component.js
│   │       ├── integration-test.js
│   │       └── template.hbs
│   ├── routes
│   │   ├── login
│   │   │   ├── acceptance-test.js
│   │   │   ├── route.js
│   │   │   └── template.hbs
│   │   └── post
│   │       ├── -components
│   │       │   └── post-viewer
│   │       │       ├── component.js
│   │       │       └── template.hbs
│   │       ├── edit
│   │       │   ├── -components
│   │       │   │   ├── post-editor
│   │       │   │   │   ├── calculate-post-title.js
│   │       │   │   │   ├── component.js
│   │       │   │   │   └── template.hbs
│   │       │   │   ├── route.js
│   │       │   │   └── template.hbs
│   │       │   ├── route.js
│   │       │   └── template.hbs
│   │       ├── route.js
│   │       └── template.hbs
│   ├── styles
│   │   └── app.scss
│   └── index.html
└── utils
    └── md5.js</code></pre>
<p>Going though the folders from top to bottom, because dev.to doesn't allow inline links without code fences… (a great feature of one of <a href="https://prismjs.com/">prism.js'</a> plugins).</p>
<h3 id="src"><code>src</code></h3>
<p>Most of this will focus on the <code>src</code> directory, as any other top-level folder or file tends to be more project or ecosystem specific, and may not generally translate to projects cross-ecosystem. Some examples of those folders that may not translate due to project-specific or build-configuration-specific reasons are: <code>app/</code>, <code>tests/</code>, <code>vendor/</code>, <code>public/</code>, <code>config/</code>, <code>translations/</code>, etc.</p>
<h3 id="srcdata"><code>src/data</code></h3>
<p>This directory is intended for all api-related data interactions and representations. In an app where you have the model-adapter-serializer pattern, you may want additional folders within <code>src/data</code> such as <code>models</code> or <code>transforms</code>, depending on how much normalization you desire within your application. This is why it doesn't necessarily make sense to have anything named more specific or vague.</p>
<h3 id="srcredux-store"><code>src/redux-store</code></h3>
<p>If using redux, most guides and tutorials just use the same <code>store</code>, which can be ambiguous, since <code>store</code> is a construct used by any library that maintains a cache of data. So not only in <a href="https://redux.js.org/api/store">Redux</a>, but also in <a href="http://orbitjs.com/v0.15/guide/#Orbit-primitives">Orbit.js</a>, and <a href="https://guides.emberjs.com/release/models/#toc_the-store-and-a-single-source-of-truth">ember-data</a>.</p>
<p>For more info on app-level state management, <a href="https://www.developertown.com/react-vs-ember-part-2-state-management/">See this article comparing state mangement in both React and Ember</a></p>
<h3 id="srcui"><code>src/ui</code></h3>
<p>The entirety of anything that directly affects the display should go in the <code>ui</code> folder. This includes styles, components, and routes. The user interface can exist independent of data, application state, and utilities.</p>
<h3 id="srcuiroutes"><code>src/ui/routes</code></h3>
<p>Most single page apps are using some sort of router, and therefor the UI is entirely route based. What components display are determined by what routes are active. Due to this coupling of display, and consequently, behavior with the browser URL, it should only be natural to divide up your app by the natural route boundaries. Splitting the UI by route also lends itself to straight-forward code-splitting on the route boundaries.</p>
<h3 id="srcuiroutesroute-name-components"><code>src/ui/routes/{route-name}/-components</code></h3>
<p>In a <a href="https://github.com/sillsdev/appbuilder-portal/tree/33f2ada4c2601d6586ca89b934f83dae0d44e5b0/source/SIL.AppBuilder.Portal.Frontend/src/data">recent React project</a>, I've tried to omit the route-level private components directory, but it's lead to confusion between what is intended for the route, and what is there to support what is rendered on the route.  I had originally omitted the <code>-components</code> directory thinking that if I/my team just use the right folders, <a href="https://github.com/sillsdev/appbuilder-portal/tree/ee84202aa0717191c03a12f51c5542cfb02222c5/source/SIL.AppBuilder.Portal.Frontend/src/ui/routes/project">things wouldn't be so bad</a>.</p>
<p>An example of a page where you'd want nested routes separate from your components is tabbed navigation:</p>
<pre><code>posts/post
├── view/
├── comment-moderation/
├── publishing-options/
│   ├── -components/
│   │    ├── confirm-publish-modal.jsx
│   │    └── social-media-blast-options.jsx
│   └── index.jsx
└── edit/
    ├── -components/
    └── index.jsx</code></pre>
<p>This structure, unlike the above link (<em><a href="https://github.com/sillsdev/appbuilder-portal/tree/ee84202aa0717191c03a12f51c5542cfb02222c5/source/SIL.AppBuilder.Portal.Frontend/src/ui/routes/project">things wouldn't be so bad</a></em>), this has a clear, explicit separation of components and route-specific components.  In the <a href="https://github.com/sillsdev/appbuilder-portal/tree/ee84202aa0717191c03a12f51c5542cfb02222c5/source/SIL.AppBuilder.Portal.Frontend/src/ui/routes/project">linked react app</a>, I've also been playing with keeping local-only higher-order components (HoCs) at the top route-level due to their one-time use nature -- though, in this particular app, <a href="https://github.com/sillsdev/appbuilder-portal/blob/ee84202aa0717191c03a12f51c5542cfb02222c5/source/SIL.AppBuilder.Portal.Frontend/src/ui/routes/project-directory/index.tsx#L96">commonly-used</a> HoCs are moved to the data directory. I'm still kind of playing around with the idea, but the HoC locations are more specific to the functional single-page-apps such as those that would be react-based.</p>
<p>One criteria to use to know if your structure is heading in the right direction is how often you end up using <code>../</code> or <code>../../</code> in your import paths.  Using upwards reverse relative paths violates our <code>Goal #5</code> stating that any subtree can change location and the functionality of the contents should remain in a working state. The above example should not inherently have any reverse-relative pathing.</p>
<p>An example violating <code>Goal #5</code>:</p>
<pre><code>posts/post
├── view/
├── comment-moderation/
├── publishing-options/
│   └── index.jsx
├── confirm-publish-modal.jsx
├── social-media-blast-options.jsx
└── edit/
    └── index.jsx</code></pre>
<p>Here, <code>publishing-options</code> files must use <code>../</code> to access the components defined at the parent level.</p>
<h3 id="srcutils"><code>src/utils</code></h3>
<p>Any functions, classes, or utilities should live in <code>src/utils</code>. These files should be purely unit testable, as they should have no app dependencies. This includes things like string format conversion, auth0 wrappers, <code>fetch</code> abstractions, etc.</p>
<h3 id="overall">Overall</h3>
<p>Let's revisit our goals, and look out how this proposed layout meets each one:</p>
<p><strong>1)</strong> <em>Users should be able to maintain their apps without worrying about the structure of their imports inhibiting them from making changes.</em></p>
<p>Achieving this goal is mostly through simply having <em>any</em> documented convention that can be referenced later. There are currently no general static analysis tools to help out with <em>enforcing</em> a structure -- though, there is one tool for one of the major frameworks that dictates structure. (See <em>Implementation</em> below)</p>
<p><strong>2)</strong> <em>Related files should be discoverable, such that a user does not need to hunt for a file should they not be using TypeScript (where you'd be able to use "Go to definition"</em></p>
<p>By having related files next to each other in this layout, everything is contextual by nature. If someone is a heavy file-tree/project-tree browser, they'll have an easy time navigating and discovering what they're working on and what is involved.</p>
<p><strong>3)</strong> <em>Related files should be accessible, such that a user can easily locate a related file without having any IDE features (i.e.: browsing on github).</em></p>
<p>This is related to (2), but more enforces co-location. When browsing files quickly online, without editor or typescript features, it's convenient to be able to click through as few web pages as possible to view related components.</p>
<p><strong>4)</strong> <em>Users should see have reasonable context at any level within their project hierarchy. Flattening out too much _is</em> overwhelming and reduces the ability to maintain, discover, and access._</p>
<p>By having a nested structure by route, any component that is only used in one place will by contextually co-located to its usage. This keeps the amount of large flat folders to a minimum, and allows for understand the greater design of the app without having to follow references everywhere.  Sibling folders are to be treated as complete unrelated (adopted?).</p>
<p><strong>5)</strong> <em>Refactoring sections of the project should be easy. When moving a directory to a new location, the internal behavior should remain functional.</em></p>
<p>I hope this one is self-explanatory, but this folder/file structure allows for drag-and-drop refactoring where any folder moved should have all of its internal tests still passing.</p>
<p><strong>6)</strong> <em>The right way and place to add a new thing should be obvious and a structure should not allow for unnecessary decisions.</em></p>
<p>This, in part, relies on both documentation and programmatic enforcement. The structure follows a strict set of rules that can be easily learned. For example, when using this folder/file stricture, by default, things should be going in <code>-components</code> folders as you build out a route. For more inspiration on what kind of rules there could be, read about <a href="https://github.com/emberjs/rfcs/blob/master/text/0143-module-unification.md">The Octane layout (formally Module Unification)</a></p>
<p><strong>7)</strong> <em>Tests and styles should be co-located along side components.</em></p>
<p>Instead of in a top-level <code>tests/</code> directory, tests can be contextually located with the thing that they are testing. This works for unit, integration, and acceptance tests. There will, of course, be exceptions to this, where you may be testing something app-wide and it has no specific context -- for those situations, I tend to just put tests in <code>tests/acceptance/</code> (if they are acceptance tests).</p>
<p><strong>8)</strong> <em>Avoid the infamous "titlebar problem", where a bunch of files all named the same can't be differentiated in the editor (though, a lot of this is editor-based)</em></p>
<p>The tab problem <em>shouldn't</em> be a thing in modern editors</p>
<p>(neo)Vim: <img src="https://thepracticaldev.s3.amazonaws.com/i/owfnzb4fkonys4uoex7t.png" alt="tabs vim" /><br />
VSCode: <img src="https://thepracticaldev.s3.amazonaws.com/i/bi4upaoiypp4vsxuaano.png" alt="tabs vscode" /><br />
Atom: <img src="https://thepracticaldev.s3.amazonaws.com/i/2cb0ut4lwxfqn4pnc689.png" alt="tabs atom" /></p>
<p><strong>9)</strong> <em>The structure should not impose limitations that would prevent technical advancement -- such as the addition of <a href="https://survivejs.com/webpack/building/code-splitting/">code-splitting</a> to a project that does not yet have it.</em></p>
<p>Because the files locations can be fitted to a rule, (i.e: <code>src/${collection}/${namespace}/${name}/${type}</code>), we can programatically crawl across the project and experiment with 'conventions', or compile scss without importing into the javascript, or invoke some transform on a particular sub-tree of the project.</p>
<p>A more concrete / real-world example (in user-space), by having the files split apart by route, we allow the file system to know our natural route/code-splitting boundaries -- which makes for a much easier implementation of code-splitting.</p>
<h2 id="implementation">Implementation</h2>
<ol>
<li>How do you get everyone on the same page when anything can go?</li>
<li>How do you achieve consistency between developers?</li>
<li>How do you remember where something <em>should</em> go?</li>
<li>How do you manage imports with all these file trees?</li>
</ol>
<p>For 1 through 3, the only answer for most projects is in-depth code reviews. After the first few established routes, it'll get easier to maintain. But it is inevitably a manual process, as most ecosystems do not have a way to programatically enforce conventions.</p>
<p>For managing imports, the best thing to do is to set up absolute aliases to common entry points.</p>
<p>For example:</p>
<pre><code>    "paths": {
      "project-name/*: ["."],
      "@data/*": ["src/data/*"],
      "@models/*": ["src/data/models/*"],
      "@ui/*": ["src/ui/*"],
      "@components/*": ["src/ui/components/*],
      "@env": ["src/env.ts"],
      "tests/*": [ "tests/*" ],
      "*": ["types/*"],</code></pre>
<p>This does mean that if you have deeply nested components, your import paths may be long, but they are easy to <code>grep</code> for, and you'll have an easier time moving subtrees around since there are no relative paths to worry about breaking.</p>
<p>An example of a React app implementing most of the criteria outline in this post: <a href="https://github.com/sillsdev/appbuilder-portal/tree/33f2ada4c2601d6586ca89b934f83dae0d44e5b0/source/SIL.AppBuilder.Portal.Frontend/src/data">Example React App</a></p>
<p>However, in Ember, there is a <a href="https://github.com/ember-cli/ember-resolver">resolver</a>.  The resolver defines a set of rules for finding things and contextually discovering components, routes, data models, etc. There are a set of conventions that allow the resolver to find things in app-space, so that you don't need to worry about importing them. There is a reference, the resolver looks up the reference, and the thing stubbed in.</p>
<p>Something unique about ember, is that it has a bunch of build-time optimizations that the other ecosystems don't have. This is powered by broccoli, where you can transform parts of your app file tree during the build process. Ember uses this to swap out lookups with the actual reference to a component (for example, could be other things). Broccoli is also used to swap out simple helpers such as <code>{{fa-icon}}</code> with the rendered html during build so that the bundle can be smaller.</p>
<p>To read more about ember's resolver, feel free to checkout <a href="https://dockyard.com/blog/2016/09/14/understanding-ember-s-resolver">DockYard's article, "Understanding Ember's resolver"</a><br />
To read more about Broccoli, Oli Griffith has an <em>amazing</em> <a href="http://www.oligriffiths.com/broccolijs/">guide / tutorial on it</a></p>
<p>An example of this structure can be found here:<br />
<a href="https://gitlab.com/NullVoxPopuli/emberclear/tree/master/packages/frontend">emberclear at gitlab</a> (this is the code for <a href="https://emberclear.io">emberclear.io</a>, one of my side projects).</p>
<p><a href="https://github.com/emberjs/rfcs/blob/master/text/0143-module-unification.md">The Octane Layout's</a> folder structure satisfies nearly all use cases. And the majority of this post represents a subset of the ideas from the The Octane Layout's RFC.</p>
<p>Note that the Octane layout is not yet released. It's coming early 2019, along with the release <a href="https://github.com/tomdale/rfcs/blob/2018-roadmap/text/0000-roadmap-2018.md">Ember Octane</a></p>
<p>Would I say that this in <em>the</em> layout people should use? maybe. There is some breathing room between what I've outlined for all js ecosystems to use and what the Octane layout dictates for ember-specific apps. Ultimately, if you are in an ecosystem where you have to decide how to lay things out, just keep the guidelines in mind as your placing files around, or copy everything here -- but with some tweaks. Ultimately, you need to do what is best for your team. Personally, with React, I feel <em>close</em>. Maybe there is a tool that could be written for non-ember projects that helps guide structure. Like a linter, but for file locations.</p>]]></description><link>https://nullvoxpopuli.com/2019-04-15-a-general-project-structure-that-works-in-any-ecosystem</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-04-15-a-general-project-structure-that-works-in-any-ecosystem</guid><pubDate>Mon, 15 Apr 2019 10:44:47 GMT</pubDate></item><item><title><![CDATA[My desires for the Ember 2019 roadmap]]></title><description><![CDATA[<p>I've been a huge fan of ember's progress over the past year and a half, and while I've been riding the canary release train the entire time, enjoying (testing/debugging) features before everyone else does, it's relieving to finally see so many of the features that bring ember into the current state of javascript finally landing. Some may doubt this, but I've been holding back on promoting ember because I believe in the vision of the future (which is nearly now), the vision of Octane, where ember is <em>just</em> a thin layer on top of native javascript -- which is immensely important to reducing the learning curve and improving adoptability and searchability.</p>
<p>This post is inspired by the <a href="https://blog.emberjs.com/2019/05/20/ember-2019-roadmap-call-for-posts.html">call for blog posts to determine the Ember 2019 roadmap</a> (and <a href="https://emberjs.com/blog/2018/05/02/ember-2018-roadmap-call-for-posts.html">the one from last year</a>) -- while, over the past years I have not worked with ember professionally, I've enjoyed being involved in the community and being involved with the framework as whole as a hobby, there is no greater feeling than taking long breaks from a side project, and knowing exactly where you left off without any catchup/recall cost (I know this is hyperbole.. shhh). However, as I've been trying to stay up to date with the other ecosystems, mainly React, as that's been what I've professionally been doing the past almost three years, I've made note of things that are high on the hype train that I think will allow for ember's adoption to soar.</p>
<h2 id="octane">Octane</h2>
<p>This is the edition that was started as a result of last years community blogging efforts. Ember, over the years, and especially with Module Unification, has kind of had a reputation for over-promising and under-delivering -- so it would be totally stellar to see this released soon. As far as I'm aware, there is only one major feature flag still on canary that has yet to make it to enabled by default, <code>EMBER_METAL_TRACKED_PROPERTIES</code>. Then the only remaining things are documentation. Which, I'll be helping with at the end of May as I'll be between jobs for a couple weeks. Exciting!</p>
<h2 id="website-redesign">Website Redesign</h2>
<p>The <a href="https://github.com/emberjs/rfcs/pull/425">Website Redesign RFC</a> was merged at the beginning of April of this year. It looks amazing! With the upcoming Octane changes, and entire paradigm shift in how ember is to be developed, implementing the redesign shortly after the release of Octane would be amazing. I mean, <a href="https://github.com/wifelette/rfcs/blob/master/text/0425-website-redesign.md#detailed-design">look at these pictures</a>. (I'm very excited about this, but I have no idea on progress or timeline). With ember becoming more and more modern as time progresses, it's time we get the site up to date as well. It could hurt ember's growth otherwise.</p>
<h2 id="project-structure">Project Structure</h2>
<p><a href="https://blog.emberjs.com/2019/03/11/update-on-module-unification-and-octane.html">Module Unification was originally planned to ship with Octane</a> but as people were playing with it, it become clear that it was uncovering more questions about ergonomics than answers. Most notably (that I dealt with anyway) are local-lookup rules for tests, helpers, modifiers, everything. With the current state of Module Unification there are a lot of implicit rules that the developer needs to know about in order to use it effectively. Through bugging a bunch of people on the ember discord early on in 2018, I learned all the tips and tricks when implementing <a href="https://emberclear.io">emberclear</a> (<a href="https://github.com/NullVoxPopuli/emberclear/tree/master/packages/frontend">source</a>) -- but expecting everyone, especially those not comfortable with ember to begin with, to learn all that is asking too much.</p>
<p>There are a couple new~ish RFCs to <em>help</em> with part of this problem, <a href="https://github.com/emberjs/rfcs/pull/454">SFC & Template Import Primitives</a> and <a href="https://github.com/emberjs/rfcs/pull/481">Component Templates Co-location</a>. The SFC and Template Import Primitives one will allow addon authors to experiment with different project structures, as components could be imported from wherever, eliminating the need to memorize implicit local lookup rules and building on existing knowledge in how JavaScript module imports work. This RFC would also allow for Single-File Components which would be super handy for little things like buttons, links, etc. The Component Template Co-Location RFC will bring pods-like co-location of component and template files to the classic layout, which will be a huge improvement to project-browseability. I'm violently against the classic layout, so this would be a very welcomed change.</p>
<h2 id="query-params">Query Params</h2>
<p>Query Params are something that everyone agrees needs to be better. <a href="https://github.com/offirgolan/ember-parachute">ember-parachute</a> has taken a stab at making query params better. I've submitted an <a href="https://github.com/emberjs/rfcs/pull/380">RFC</a> and implemented a <a href="https://github.com/NullVoxPopuli/ember-query-params-service">prototype addon</a> that uses decorators to totally abstract away the boilerplate atd awkward APIs that the query params documentation currently instructs us to use -- bringing query param usage up to date with the rest of the modern world.</p>
<p>All I want is something like this:</p>
<pre><code class="ts language-ts">import Route from '@ember/routing/route';
import { queryParam } from 'ember-query-params-service';

export default class ApplicationRoute extends Route {
  @queryParam('r') isSpeakerNotes;
  @queryParams('slide') slideNumber;

  model() {
    return {
      isSpeakerNotes: this.isSpeakerNotes,
      slideNumber: this.slideNumber
    }
  }
}</code></pre>
<p>where the <code>@queryParam</code> decorator can be used <em>anywhere</em> -- components, routes, etc.</p>
<h2 id="better-error-and-loading-state-handling">Better Error and Loading state handling</h2>
<p>According to the routing guides on <a href="https://guides.emberjs.com/release/routing/loading-and-error-substates/">error and loading substates</a>, for both errors and loading state, we need a template in various locations depending on our route structure and, for loading, which routes we think are slow enough to warrant a loading indicator (whether that be a spinner, or fake cards or other elements on the page). Additionally, there is a loading action fired per route that can optionally be customized in order to set parameters on the route's controller. I think this is needlessly complicated, and we can do better.</p>
<p>Maybe something we could do to make things less configuration-based and also be more explicit is to set properties on routes.</p>
<pre><code class="ts language-ts">import Route from '@ember/routing/route';
import ErrorComponent from 'my-app/components/error-handler';
import LoadingComponent from 'my-app/components/loading-spinner';

export default class MyRoute extends Route {
  onError = ErrorComponent;
  onLoading = LoadingComponent;

  async model() {
    const myData = await this.store.findAll('slow-data');

    return { myData };
  }
}</code></pre>
<p>This is just spit-balling here, but setting components to use as the error and loading state render contexts allows us to more intuitively tie in to the dependency injection system as well as have very clear shared resources for this common things. The actions that are called on error and loading could still exist, as those may be useful for maybe redirecting to different places in case of a 401 Unauthorized / 403 Forbidden error, but the main thing that I want, specifically for error states is that for <em>any</em> error that occurs within a route's subtree, the onError component should be rendered. This would catch errors that currently break the UI entirely, or are only printed to the console. Not only should network errors that occurs within the beforeModel, model, and afterModel hooks, but also during rendering. With component centric development, errors can happen anywhere, and it would be fantastic to have a centralized placed to handle those errors.</p>
<p>This is somewhat inspired by some React work I've done recently, where instead of directly using the react-router Routes, I wrapped them in an <code>ErrorBoundary</code>, which does exactly as I describe above. Any error that occurs, whether during rendering or otherwise is caught be the ErrorBoundary, and then an error-parsing component is rendered that can display the error in a meaningful way to the whomever is looking at the screen -- all without having to have the console open. It's great for that added insight into what's going on when the UI doesn't have as expected.</p>
<h2 id="route-splitting--svelte-builds">Route Splitting &amp; Svelte Builds</h2>
<p>In a world where most online devices are low-powered pocket-computers, to reach the broadest possible audience, it's important now, more than ever, that we pay attention to how our apps are shipped to each device. <a href="https://github.com/embroider-build/embroider">embroider</a> is kinda beta~ish atm and enables route-splitting. I've done route-splitting in React, and other webpack-using projects… and it feels very manual. With ember I expect things to just work without having to configure things, and as I read the embroider readme right now, there is a <code>splitAtRoutes</code> option to enable, to make things "just work", no need to <code>await import('file')</code>, no need to come up with a loading scheme for your router to wait for the route subtree to load -- embroider does that all for you, which is super exciting! (Seriously, I can't express how excited I am for embroider to become the main build system for ember apps -- I've been in the React community so long, and hearing everyone make an excessive big deal over tiny apps needing route splitting and then watching all the hoops they have to jump through to implement it is exhausting… it's so relieving to to see a proper implementation). I would like to see embroider as a default sooner, rather than later, especially as Octane brings in a new wave of fresh blood, people are going to be wondering where the features are that embroider provides.</p>
<p>Additionally, along the same lines of shrinking bundle size, Svelte builds are to strip out unused features in ember. I don't know if this work has started, and I'm still a little fuzzy on the details, but it seems like for the crowd that are targeting PWAs and very low powered devices, where JS parse time is a serious issue, this would be a huge improvement -- for everyone.</p>
<h2 id="data">Data</h2>
<p>The changes I want to see in the data space may not exactly be ember-data related, but I know that there is a bunch of incremental progress to make the ember-data experience both more flexible and more friendly.  The biggest things I want to see from data as a whole is for the <a href="https://jsonapi.org"><code>{ json:api }</code></a> Operations Spec to land, so that we can have worry-free implementations. Operations is really exciting to me, because it brings parity to GraphQL while still having strong semantic correctness with relational data. Operations allow for easier websocket implementations while still using the <code>{ json:api }</code> format -- and maybe more importantly, allows for sanctioned bulk behavior without hacks.  Operations, generally, can be used today, but not with ember-data -- I'm very excited with the direction ember-data has been going lately, and I'll be even more excited as operations start to solidify.</p>
<hr />
<p>Those are my main focuses this year, and I'll probably try to find a way to help progress each of them, as I'll actually be working with ember for the better half of this year. I'm very excited about all of this, and want it all <em>now</em>. :)</p>]]></description><link>https://nullvoxpopuli.com/2019-05-14-ember-2019-roadmap</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-05-14-ember-2019-roadmap</guid><pubDate>Wed, 15 May 2019 02:16:07 GMT</pubDate></item><item><title><![CDATA[React Response: Hot Loading]]></title><description><![CDATA[<h1 id="react-response-setting-up-hot-module-replacement">React Response: Setting up Hot Module Replacement</h1>
<p>Partially due to the fact that I find coming up with things to write about somewhat difficult, I've been seeking articles from the React community to rewrite for Ember (primarily <a href="https://twitter.com/nullvoxpopuli/status/1134602455088619521">on twitter</a>). This'll help the searchability of common patterns people may be familiar with when coming from React, or any other ecosystem which also has a similar nomenclature.</p>
<p>My hope for this series is only two things:</p>
<ul>
<li>Improve the perception of Ember with respect to modern features and behavior</li>
<li>Show how conventions and architectural patterns can make everyone's lives easier.</li>
</ul>
<p>In response to <a href="https://thoughtbot.com/blog/setting-up-webpack-for-react-and-hot-module-replacement">Setting Up Webpack for React and Hot Module Replacement</a> (by thoughtbot), as proposed by <a href="https://twitter.com/j_mcnally/status/1134844414256386048">@j_mcnally</a>, I'll be going through the article in chunks as there are correlations with how all of what is explained could be done in an ember project.<br />
The article is broken out into a few short sections:</p>
<ol>
<li><p>Initializing a project</p></li>
<li><p>Setting up webpack</p>
<p>a. Basic configuration</p>
<p>b. Adding loaders</p>
<ol start="3">
<li>Writing React components</li></ol>
<p>a. Adding an index page</p>
<ol start="4">
<li>Setting up the webpack dev server</li></ol>
<p>a. Hot module replacement</p></li>
</ol>
<p>It's a short article, and the first half is setting up the project -- so, if you have a terminal shell ready and are all setup for javascript development, feel free to run the following command to instantiate your project, and we can skip parts <code>1</code>, <code>2</code>, <code>2a</code>, <code>2b</code>, <code>3a</code> and <code>4</code> (we'll get to <code>4a shortly</code>).</p>
<p>Note: if all you care about is how to do Hot module replacement, feel free to <a href="#hmr">skip ahead</a></p>
<pre><code class="bash language-bash">npx ember-cli new hmr-demo -b @ember/octane-app-blueprint</code></pre>
<p>Depending on your familiarity with ember, it may seem like this <em>isn't enough</em> to set up a project. Well, my friends, with the power of conventions and agreed upon tooling, the above command bundles all the above setup steps so that you don't need to care about them when making apps.  Sure, if you enjoy tweaking webpack configs to try to squeeze out more loading performance or reduce bundle size in a variety of ways, that's fine -- but for <em>being productive in feature development</em>, it's not something that needs to be repeated for every project -- even in the React world, and especially at my previous company <a href="http://developertown.com/">DeveloperTown</a> (a consultancy), configuration files were copied and pasted between projects.</p>
<p>Next up, let's look at writing components in Ember -- by rewriting the "Greeting" component from the thoughtbot article.</p>
<pre><code class="bash language-bash">yarn ember g component greeting</code></pre>
<p>This creates 3 files as shown in this screenshot of the terminal output:<br />
<img src="/images/post-2019-06-generate-greeting-component.png" alt="the output from running yarn ember g component greeting" /></p>
<p>While the generate component command gives you 3 files, you don't <em>need</em> each of them. The generate command gives you those files so that, for most components, you have the availability to quickly open and edit, without having to create the files yourselves and implement the boilerplate.</p>
<p>in <code>app/templates/components/greeting.hbs</code>, we'll type out a little template that says Hello to whatever name we pass in. We can ignore the greeting.js and greeting-test.js files for now.</p>
<pre><code class="handlebars language-handlebars">&lt;div class='greeting'&gt;
  Hello, {{@name}}!
&lt;/div&gt;</code></pre>
<p>Inside of <code>app/templates/application.hbs</code>, the entrypoint to rendering our application, we need to render our <code>Greeting</code> component.</p>
<pre><code class="handlebars language-handlebars">&lt;Greeting @name="Kerrigan" /&gt;

{{outlet}}</code></pre>
<p>A note about the syntax used here, if coming from React, or any other ecosystem, the <code>@</code> used when invoking a component signifies that the key-value pair is an argument, and not an attribute, such as <code>class</code> or <code>data-test</code> would be. This allows for some nice API design when building UI components where you want to give control of attribute values to the caller. For more information on this, see <a href="https://twitter.com/pzuraq">@pzuraq</a>'s blog post on <a href="https://www.pzuraq.com/coming-soon-in-ember-octane-part-2-angle-brackets-and-named-arguments/">Angle Brackets and Named Arguments</a></p>
<p><span id='hmr' /></p>
<h2 id="hot-module-replacement">Hot module replacement</h2>
<p>There is a package called <a href="https://github.com/lifeart/ember-ast-hot-load">ember-ast-hot-load</a> which will do all of the hot module replacement for us. This enables us to maintain the greater application state while we work on individual components. We don't need to wait for the entire app to rebuild, or the page to refresh. This greatly benefits our development feedback loop by reducing wait time.</p>
<p>To install the package, we can run:</p>
<pre><code class="bash language-bash">yarn ember install ember-ast-hot-load</code></pre>
<p>Now we'll want to start our development server with</p>
<pre><code class="bash language-bash">yarn start</code></pre>
<p>To see the speed that hot module replacement gives you, let's modify the greeting component by wrapping the text in an <code>h1</code> tag.</p>
<p>in <code>app/templates/components/greeting.hbs</code>:</p>
<pre><code class="handlebars language-handlebars">&lt;div class='greeting'&gt;
  &lt;h1&gt;Hello, {{@name}}!&lt;/h1&gt;
&lt;/div&gt;</code></pre>
<p>When you save the file you'll see the page update automatically without a page reload. If you don't believe it, feel free to remove the ember-ast-hot-load package from package.json and restart the dev server.</p>
<p>That's it!</p>
<hr />
<p>tl;dr:</p>
<pre><code class="bash language-bash">yarn ember install ember-ast-hot-load</code></pre>
<p>done.</p>
<h2 id="want-more-information">Want More Information?</h2>
<ul>
<li><a href="https://guides.emberjs.com/release/getting-started/quick-start/">Getting started (general)</a></li>
<li><a href="https://guides.emberjs.com/release/templates/handlebars-basics/">Templates in Ember</a></li>
<li><a href="https://guides.emberjs.com/release/components/defining-a-component/">Components</a></li>
<li><a href="https://guides.emberjs.com/release/addons-and-dependencies/managing-dependencies/">Addons and Dependencies</a></li>
<li><a href="http://emberatlas.com">The Ember Atlas</a><ul>
<li><a href="https://www.notion.so/Ember-For-React-Developers-556a5d343cfb4f8dab1f4d631c05c95b">Ember for React Developers</a></li></ul></li>
</ul>]]></description><link>https://nullvoxpopuli.com/2019-06-01-react-response--hot-loading</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-06-01-react-response--hot-loading</guid><pubDate>Sat, 01 Jun 2019 21:10:54 GMT</pubDate></item><item><title><![CDATA[Ember Concurrency]]></title><description><![CDATA[<h1 id="concurrency-the-problems-you-dont-know-you-have">Concurrency, the problems you don't know you have</h1>
<p>In user interface development, there are many intermediate states that must be accounted for.<br />
A user may click a button that triggers something that will take a while, such as an API request.<br />
Maybe a websocket connection needs to be established,<br />
or there is a page with search or autocomplete capabilities.<br />
<a href="https://ember-concurrency.com">ember-concurrency</a> solves a number of problems<br />
with dealing with intermediate state in both user interaction and background async behavior.<br />
Let's take a look at what ways that ember-concurrency makes things easier,<br />
and what ways it's not needed.</p>
<blockquote>
  <p>Note: this post will be kept up to date with the latest decorators and<br />
  concurrency documentation as the decorators proposal and<br />
  babel transform support changes / improves.</p>
</blockquote>
<p>Packages Versions at the time of writing:</p>
<ul>
<li>ember-cli-babel: 7.11.0</li>
<li>ember-concurrency: 1.0.0</li>
<li>ember-source: 3.14.0-canary</li>
</ul>
<p>As a disclaimer: this post is not comprehensive, and there are likely additional use cases for both using and <em>not</em> using ember-concurrency.</p>
<p><strong>Table Of Contents</strong></p>
<ul>
<li><a href="#submitting-a-form">Submitting a Form</a></li>
<li><a href="#examples">Examples</a><ul>
<li><a href="#async-button">Async Button</a></li>
<li><a href="#text-search">Text Search</a></li></ul></li>
<li><a href="#further-reading">Further Reading</a></li>
</ul>
<h2 id='submitting-a-form'>Submitting a form</h2>
<p>Forms can be used for creating and updating data. Given that we have the following form:</p>
<pre><code class="handlebars language-handlebars">&lt;form {{on 'submit' this.onSubmit}}&gt;
  &lt;button type='submit'&gt;Save&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>Every time the user triggers the form's submit, <code>this.onSubmit</code> will be invoked. That sounds exactly what we want right? Well, not necessarily. Maybe <code>onSubmit</code> is defined as:</p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class MyForm extends Component {
  @action
  async onSubmit() {
    await fetch('https://my.api/resource', { method: 'POST' });
  }
}</code></pre>
<p>if the network is laggy, or if the user's browser hangs for whatever reason,<br />
the user may get impatient and trigger the submit action again.<br />
If this API endpoint is creating a new record on every request,<br />
we now have duplicate data.<br />
To protect against duplicating data,<br />
we'll need to track state inside the submit action,<br />
and represent that state on the form.</p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class MyForm extends Component {
  @tracked isSubmitting = false;

  @action
  async onSubmit() {
    if (this.isSubmitting) {
      return;
    }

    this.isSubmitting = true;

    await fetch('https://my.api/resource', { method: 'POST' });

    this.isSubmitting = false;
  }
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;form {{on 'submit' this.onSubmit}}&gt;
  &lt;button type='submit' disabled={{this.isSubmitting}}&gt;Save&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>We've now doubled the amount of code in this example.</p>
<p>Unfortunately, assuming we're writing tests for our code, we may accidentally discover an error during our tests.</p>
<blockquote>
  <p>Called set on destroyed object</p>
</blockquote>
<p>To resolve this, after every <code>await</code>, we need to check to see if our component has been destroyed.<br />
Our action now becomes:</p>
<pre><code class="ts language-ts">  async onSubmit() {
    if (this.isSubmitting) {
      return;
    }

    this.isSubmitting = true;

    await fetch('https://my.api/resource', { method: 'POST' });

    if (!this.isDestroyed &amp;&amp; !this.isDestroying) {
      this.isSubmitting = false;
    }
  }</code></pre>
<p>This problem is exacerbated if our action has multiple <code>await</code>ed function calls. There is a possibility of our context becoming destroyed after every <code>await</code>!</p>
<p>If there are many forms for dealing with various resources,<br />
this becomes <em>a lot</em> of boilerplate --<br />
which will grow in to a lot of difficult to maintain code as your project teams grow.<br />
Inconsistencies will be introduced due to varying implementations or<br />
people's perspectives on what state should be managed,<br />
and what actions need to be protected. So what do we do?<br />
How do we ensure a consistent implementation for all of this type of behavior?</p>
<p><em>ember-concurrency</em>.</p>
<p>The above example, could be re-written as:</p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { task } from 'ember-concurrency';

export default class MyForm extends Component {
  @(task(function*() {
    yield fetch('https://my.api/resource', { method: 'POST' });
  }).drop())
  onSubmit;
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;form {{on 'submit' (perform this.onSubmit)}}&gt;
  &lt;button type='submit' disabled={{this.onSubmit.isRunning}}&gt;Save&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>We're back to having minimal code.<br />
Not only do we no longer need to track the running state,<br />
but <em>the destroyed state is handled for us</em>.</p>
<p>Notes on the new APIs introduced:</p>
<ul>
<li><p><code>task</code></p>
<p>The <code>task</code> function handles all the "state" of the async behavior.<br />
This'll include running, not running, errored, how many concurrent task there are,<br />
what the last result or error was. For more information on <code>task</code>,<br />
<a href="http://ember-concurrency.com/api/global.html#task">see the <code>task</code> documentation</a>.</p></li>
<li><p><code>yield</code></p>
<p>This is a keyword used in generators to <em>yield</em> control back to the calling context.<br />
In this case, the calling context is more or less abstracted away from us.<br />
It enables the function passed to <code>task</code> to be cancelled, or restarted,<br />
which get to the importance of shortly.</p></li>
<li><p><code>drop</code></p>
<p>This is an ember-concurrency api on the <a href="http://ember-concurrency.com/api/TaskProperty.html"><code>Task Property</code></a>.<br />
It signifies the type of behavior we want. In this example,<br />
we want subsequent requests to be dropped or ignored,<br />
as we want to wait for the first request to be completed before allowing a subsequent request.<br />
This is important, because maybe the form won't even be on the page when the task finishes.<br />
A common pattern for CRUD is to redirect to a newly created resource for viewing,<br />
and this would enable that behavior to safely be <em>performed</em>.</p></li>
<li><p><code>perform</code></p>
<p>A template helper that returns a function that invokes <code>perform</code> on the task.<br />
The value returned by <code>task</code> isn't a function itself,<br />
but a <code>Task</code> that has a <code>perform</code> method.<br />
The <code>Task</code> encapsulates the state of the async behavior,<br />
and <code>perform</code> is how an instance of that behavior is created / started.</p></li>
</ul>
<h2 id='when-not'>When wouldn't you use ember-concurrency?</h2>
<p><code>ember-concurrency</code> is not a replacement for <code>async</code>/<code>await</code> behaviors. It's a supplement. The rule of thumb is:</p>
<blockquote>
  <p>Use <code>async</code>/<code>await</code> when your function has no side-effects on the calling context.<br />
  Use <code>ember-concurrency</code> when there are side-effects, or limiting concurrent executions of a function.</p>
</blockquote>
<p>Where a side-effect is:</p>
<ul>
<li>the setting of a variable in the invocation context (such as a service or component)</li>
<li>the triggering of another task</li>
</ul>
<p>For example, a side-effect-free function may look like this:</p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import ENV from 'app-name/config/environment';

export default class MyComponent extends Component {
  async getPosts() {
    let response = await fetch(`${ENV.host}/api/posts`);
    let json = await response.json();
    let data = JSON.parse(json);

    return data.posts;
  }
}</code></pre>
<p>It does not set any properties on the class. If the component is destroyed while the <code>fetch</code> request is in-flight -- nothing will go wrong, as there is no <code>set</code> / assignment on a destroyed component.</p>
<p>If we desire to trigger <code>getPosts</code> from a user interaction, there will need to be an <code>ember-concurrency</code> task somewhere.</p>
<pre><code class="ts language-ts">@action
async refresh() {
  let posts = await this.getPosts();

  this.posts = posts;
}</code></pre>
<p>If we were to add an action invokes <code>getPosts</code>, we would run into two problems:</p>
<ol>
<li>An error will occur "called set on destroyed object", if the component is destroyed before <code>refresh</code> finishes.</li>
<li>There is no way to prevent concurrent requests.</li>
</ol>
<p>Both of these are solved with a Task</p>
<pre><code class="ts language-ts">@(task(function*() {
  let posts = yield this.getPosts();

  this.posts = posts;
}).drop())
refresh;</code></pre>
<p>It looks almost the same, except the task is cancelled when the component is destroyed, and all subsequent calls to <code>refresh</code> will be ignored, until the first running invocation finishes. But while <code>refresh</code> <em>must</em> be a task. <code>getPosts</code> can remain a vanilla JavaScript <code>async</code>/<code>await</code> function.</p>
<h2 id='examples'>Examples</h2>
<h3 id='async-button'>Async Button</h3>
<p>Async buttons, or buttons that can be aware of the rejected or resolved states of a promise,<br />
are a common pattern for one-click triggers of async behavior --<br />
but that API calls, waiting for something processing-intensive, etc.</p>
<p>Going forward with this post, there will be minimal prose,<br />
and mostly just before/after examples of pre/after ember-concurrency --<br />
there <em>will</em> be some explanation of why someone wouldn't want to use ember-concurrency,<br />
where appropriate.</p>
<p>Additionally, all examples will be using TypeScript to describe the API of the components.</p>
<p><strong>Before</strong></p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracked';
import { action } from '@ember/object';

interface Args {
  promise: &lt;ReturnType&gt;() =&gt; Promise&lt;ReturnType&gt;;
  disabled?: boolean;
  label: string;
}

const SHOW_SUCCESS_FOR_MS = 2000;

export default class AsyncButton extends Component&lt;Args&gt; {
  @tracked isSuccess = false;
  @tracked isRunning = false;
  @tracked isError = false;

  @tracked error?: string;

  get isIdle() {
    return !this.isRunning;
  }

  @action
  async onClick() {
    if (this.isRunning) {
      return;
    }

    this.reset();

    try {
      await this.args.promise();

      if (!this.isDestroying &amp;&amp; !this.isDestroyed) {

        this.isSuccess = true;

        await new Promise((resolve) =&gt; {
          setTimeout(
            () =&gt; this.reset(),
            SHOW_SUCCESS_FOR_MS
          );
        });
      }


      return;
    } catch (e) {
      if (!this.isDestroying &amp;&amp; !this.isDestroyed) {
        this.error = e.message;
        this.isError = true;
      }
    }


    if (!this.isDestroying &amp;&amp; !this.isDestroyed) {
      this.isRunning = false;
    }
  }

  reset() {
    this.isSuccess = false;
    this.isError = false;
    this.isRunning = true;
    this.error = undefined;
  }
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;button
  {{on 'click' this.onClick}}
  ...attributes
  disabled={{or this.isRunning @disabled}}
&gt;
  {{#if this.isIdle}}
    {{@label}}
  {{else if this.isRunning}}
    Running...
  {{else if this.isSuccess}}
    Success!
  {{else if this.isError}}
    Error: {{this.error}}
  {{/if}}
&lt;/button&gt;</code></pre>
<p><strong>After</strong></p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task, timeout } from 'ember-concurrency';

interface Args {
  promise: &lt;ReturnType&gt;() =&gt; Promise&lt;ReturnType&gt;;
  disabled?: boolean;
  label: string;
}

const SHOW_SUCCESS_FOR_MS = 2000;

export default class AsyncButton extends Component&lt;Args&gt; {
  @tracked isSuccess = false;

  @(task(function*() {
    yield this.args.promise();
    this.isSuccess = true;

    yield timeout(SHOW_SUCCESS_FOR_MS);

    this.isSuccess = false;
  }).drop())
  promiseRunner;
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;button
  {{on 'click' (perform this.promiseRunner)}}
  ...attributes
  disabled={{or this.promiseRunner.isRunning @disabled}}
&gt;
  {{#if this.promiseRunner.isIdle}}
    {{@label}}
  {{else if this.promiseRunner.isRunning}}
    Running...
  {{else if this.isSuccess}}
    Success!
  {{else if this.promiseRunner.isError}}
    Error: {{this.promiseRunner.error}}
  {{/if}}
&lt;/button&gt;</code></pre>
<h3 id='text-search'>Text Search</h3>
<p><strong>Before</strong></p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

const DEBOUNCE_MS = 250;

interface Args {
  onSearch: &lt;ResultType&gt;(text: string) =&gt; Promise&lt;ResultType&gt;
}

function waitMs(ms) {
  return new Promise((resolve) =&gt; {
    setTimeout(resolve, ms);
  });
}

export default class TextSearch extends Component&lt;Args&gt; {
  @tracked text = '';

  lastInvocation = undefined;

  async search() {
    this.lastInvocation = new Date();

    await waitMs(DEBOUNCE_MS);

    if (this.isDestroying || this.isDestroyed) {
      return;
    }

    let waitEndedAt = new Date();

    // did we search again while waiting?
    let didSearchAgain = this.lastInvocation - waitEndedAt &lt; DEBOUNCE_MS;

    if (didSearchAgain) {
      return; /* do not invoke search */
    }

    await this.args.onSearch(this.text);
  }
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;form {{on 'submit' this.search}}&gt;
  &lt;Input @value={{this.text}} /&gt;

  &lt;!-- submit on press of enter key--&gt;
  &lt;button type='submit'&gt;Search&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p><strong>After</strong></p>
<pre><code class="ts language-ts">import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task, timeout } from 'ember-concurrency';

const DEBOUNCE_MS = 250;

interface Args {
  onSearch: &lt;ResultType&gt;(text: string) =&gt; Promise&lt;ResultType&gt;
}

export default class TextSearch extends Component&lt;Args&gt; {
  @tracked text = '';

  @(task(function*(){
    yield timeout(DEBOUNCE_MS);

    yield this.args.onSearch(this.text);
  }).restartable())
  search;
}</code></pre>
<pre><code class="handlebars language-handlebars">&lt;form {{on 'submit' (perform this.search)}}&gt;
  &lt;Input @value={{this.text}} /&gt;

  &lt;!-- submit on press of enter key--&gt;
  &lt;button type='submit'&gt;Search&lt;/button&gt;
&lt;/form&gt;</code></pre>
<h2 id='further-reading'>Further Reading</h2>
<p>The <a href="http://ember-concurrency.com/docs/tutorial">ember-concurrency docs</a> have a very thorough explanation of a single example of before and after applying ember-concurrency to a problem.</p>]]></description><link>https://nullvoxpopuli.com/2019-08-27-ember-concurrency</link><guid isPermaLink="true">https://nullvoxpopuli.com/2019-08-27-ember-concurrency</guid><pubDate>Wed, 28 Aug 2019 01:17:22 GMT</pubDate></item><item><title><![CDATA[React Response: Render Props]]></title><description><![CDATA[<h1 id="react-response-render-props">React Response: Render Props</h1>
<p><em>Series Intro</em> <a href="#render-props-begin">feel free to skip</a></p>
<p>Partially due to the fact that I find coming up with things to write about somewhat difficult, I've been seeking articles from the React community to rewrite for Ember (primarily <a href="https://twitter.com/nullvoxpopuli/status/1134602455088619521">on twitter</a>). This'll help the searchability of common patterns people may be familiar with when coming from React, or any other ecosystem which also has a similar nomenclature.</p>
<p>My hope for this series is only two things:</p>
<ul>
<li>Improve the perception of Ember with respect to modern features and behavior</li>
<li>Show how conventions and architectural patterns can make everyone's lives easier.</li>
</ul>
<p><span id='render-props-begin' /><br />
Rather than a response to a particular blog featuring React, this is more of a demonstration of correlating design patterns between the two ecosystems. Thanks to <a href="https://twitter.com/vlascik/status/1134686913875664898">@vlascik</a> for the suggestion to cover React's render props pattern.</p>
<p>First, let's clarify what a render prop is for those who may not be familiar with the term. According to the <a href="https://reactjs.org/docs/render-props.html">React Documentation</a>:</p>
<blockquote>
  <p>The term <a href="https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce">“render prop”</a> refers to a technique for sharing code between React components using a prop whose value is a function.</p>
</blockquote>
<p>In React, components are <em>just functions</em>™, so any prop passed to a component whos value is a function that returns JSX is considered a "render prop".</p>
<p>Some examples:</p>
<pre><code class="tsx language-tsx">&lt;Header
  {/* profileImage is the render prop */}
  profileImage={profileProps =&gt; {
    return (
      &lt;LargeProfileImage {...profileProps} /&gt;
    )
  }
}&gt;
  &lt;AppNavigation /&gt;
&lt;/Header&gt;</code></pre>
<pre><code class="tsx language-tsx">&lt;Header&gt;
  &lt;AppNavigation /&gt;
  {/* children, an implicit render prop passed to Header when there is block content */}
  &lt;LargeProfileImage /&gt;
&lt;/Header&gt;</code></pre>
<p>where Header could be defined as:</p>
<pre><code class="tsx language-tsx">export function Header({ profileImage, children }) {
  return (
    &lt;header&gt;
      &lt;HomeLink /&gt;

      {children}

      {profileImage &amp;&amp;
        profileImage({ className: 'header-image' })
      }
    &lt;/header&gt;
  )
}</code></pre>
<blockquote>
  <p>When would someone want to use render props in React?</p>
</blockquote>
<p>When a part of a component's template needs to be different between usages of that component. Rather than using conditionals in the component (in this case <code>Header</code>), render props allow yielding control to the calling context.</p>
<p>Ember has the exact same capabilities, but under a different name: <em>"Yieldable Named Blocks"</em>. Which we'll get to after we talk about the default / easy thing to do with both ecosystems.</p>
<h3 id="children">{children}</h3>
<p>For <code>{children}</code>, however, there is an <em>exact</em> corollary in Ember: <code>{{yield}}</code>.</p>
<p>Both words have <strong>great</strong> semantic meaning as well.</p>
<blockquote>
  <p>A parent component <em>yields</em> the rendering context to its <em>children</em>.</p>
</blockquote>
<p>The default template generated for a component contains a single line: <code>{{yield}}</code>.<br />
Let's change that to mimic the React example:</p>
<pre><code class="handlebars language-handlebars">&lt;header&gt;
  &lt;HomeLink /&gt;

  {{yield}}
&lt;/header&gt;</code></pre>
<p>aside from not having the profileImage render prop, these templates are the exact same, but with <code>{children}</code> replaced with <code>{{yield}}</code></p>
<h3 id="any-other-render-prop">Any other render prop</h3>
<p>In Ember, we'd still use yield, but with a parameter to create named 'blocks' that the calling context can use. These yields with the <code>to</code> argument are "<em>Yieldable Named Blocks</em>" (available in Ember 3.20+)</p>
<p>Example using <code>Header</code>, from above:</p>
<pre><code class="handlebars language-handlebars">&lt;header&gt;
  &lt;HomeLink /&gt;

  {{yield}}

  {{yield to='image'}}
&lt;/header&gt;</code></pre>
<p>and then the calling context would look like:</p>
<pre><code class="handlebars language-handlebars">&lt;Header&gt;
  &lt;AppNavigation /&gt;

  &lt;:image&gt;
    &lt;LargeProfileImage /&gt;
  &lt;/:image&gt;
&lt;/Header&gt;</code></pre>
<h3 id="passing-arguments">Passing Arguments</h3>
<p>For both of these examples, assume there is a UI Library which provides a bunch of component primivites for constructing the common UI pattern / "Card".</p>
<p>in React:</p>
<pre><code class="tsx language-tsx">export function SomeComponent({ header, content, children, footer }) {
  return (
    &lt;Card&gt;
      {header &amp;&amp; (
        &lt;CardHeader&gt;
          {header({
            Image: CardHeaderImage,
            Icon: CardHeaderIcon
          })}
      &lt;/CardHeader&gt;
      )}

      &lt;CardContent&gt;
        {children}
        {content &amp;&amp; content()}
      &lt;/CardContent&gt;

      {footer &amp;&amp; (
        &lt;CardFooter&gt;
          {footer()}
        &lt;/CardFooter&gt;
      )}
    &lt;/Card&gt;
  )
}</code></pre>
<p>and then in the calling context</p>
<pre><code class="tsx language-tsx">export function App() {
  return (
    &lt;SomeComponent
      header={({ Image }) =&gt; {
        return (
          &lt;&gt;
            &lt;Image src='path/to-image.png' /&gt;

            My Header!
          &lt;/&gt;
        );
      }}

      footer={() =&gt; {
        return (
          &lt;button&gt;Call to Action&lt;/button&gt;
        );
      }}
    &gt;
      freely yielded content
    &lt;/SomeComponent&gt;
  );
}</code></pre>
<p>in Ember:</p>
<pre><code class="handlebars language-handlebars">{{!-- some-component --}}
&lt;Card&gt;
  {{#if (has-block 'header')}}
    &lt;CardHeader&gt;
      {{yield
        hash=(
          image=(component 'card-header-image')
          icon=(component 'card-header-icon')
        )
        to='header'
      }}
    &lt;/CardHeader&gt;
  {{/if}}

  &lt;CardContent&gt;
    {{yield}}
    {{yield to='content'}}
  &lt;/CardContent&gt;

  {{#if (has-block 'footer')}}
    &lt;CardFooter&gt;
      {{yield to='footer'}}
    &lt;/CardFooter&gt;
  {{/if}}
&lt;/CardModal&gt;</code></pre>
<pre><code class="handlebars language-handlebars">{{!-- the calling context --}}
&lt;SomeComponent&gt;
  &lt;:header as |headerComponents|&gt;
    &lt;headerComponents.image src='path/to-image.png' /&gt;

    My Header!
  &lt;/:header&gt;

  &lt;:footer&gt;
    &lt;button&gt;Call to Action&lt;/button&gt;
  &lt;/:footer&gt;

  &lt;:default&gt;
    freely yielded content
  &lt;/:default&gt;
&lt;/SomeComponent&gt;</code></pre>
<p>What's cool about named blocks is that, as a component author, you are in control of the order the components are rendered.<br />
So in the above example where header is first and footer is second, those <em>could</em> be flipped, but still rendered in the same locations,<br />
because the content is placed where the corresponding <code>{{yield to="name"}}</code> block is. I like that a lot, and it's something I always<br />
wanted when I was writing React components.</p>
<h2 id="want-more-information">Want More Information?</h2>
<ul>
<li><a href="https://emberjs.github.io/rfcs/0460-yieldable-named-blocks.html">The RFC for Yieldable Named Blocks</a></li>
<li><a href="http://emberatlas.com">The Ember Atlas</a><ul>
<li><a href="https://www.notion.so/Ember-For-React-Developers-556a5d343cfb4f8dab1f4d631c05c95b">Ember for React Developers</a></li></ul></li>
</ul>]]></description><link>https://nullvoxpopuli.com/2020-06-16-react-response--render-props</link><guid isPermaLink="true">https://nullvoxpopuli.com/2020-06-16-react-response--render-props</guid><pubDate>Wed, 17 Jun 2020 01:42:58 GMT</pubDate></item><item><title><![CDATA[How Does Ember's Dependency Injection System Work?]]></title><description><![CDATA[<h2 id="why">Why?</h2>
<p>One of the most common things I hear from people who are new to Ember,<br />
new to programming in general, or coming from another frontend ecosystem<br />
(especially React and Vue), is that they think Ember's dependency injection<br />
system is too complicated and magical --<br />
too hard to reason about or know where the injected services come from.<br />
I, too, was in that boat -- until I really dove into how it works -- it was<br />
then that I began to understand why dependency injection even exists, and<br />
how it's actually <em>simpler</em> than <em>not</em> having it at all.</p>
<h2 id="what-is-dependency-injection">What is Dependency Injection?</h2>
<p>According to <a href="https://en.wikipedia.org/wiki/Dependency_injection">Wikipedia</a></p>
<blockquote>
  <p><em>dependency injection</em> is a technique in which an object receives other<br />
  objects that it depends on.</p>
</blockquote>
<p><em>That's it</em>.</p>
<p>So… this is dependency injection?</p>
<pre><code class="js language-js">let foo = new Foo()

let bar = new Bar(foo);</code></pre>
<p>yes!.</p>
<p>The big deal with dependency injection usually comes from <em>managing</em> how an object<br />
receives those other objects.</p>
<h2 id="why-use-dependency-injection">Why use Dependency Injection?</h2>
<p>For me personally, there are two reasons:</p>
<ol>
<li>Application State (data and functions) can be easily shared between components</li>
<li>Testing is much easier and can be done in isolation</li>
</ol>
<p>For #1, there are many ways to share state between components, but I like that<br />
dependency injection provides a centralized pattern and location for that state<br />
as well as an ergonomic and light way to interact with that state.</p>
<p>For #2, this is a little harder to boil down to a sentence or two, and ultimately<br />
comes down overall architecture of your app, how big your app is, and what sorts of<br />
things provide value when tested. For example, let's say you have some behavior<br />
for interacting with an external API, maybe it's the <a href="https://swapi.dev/">Star Wars JSON api</a>,<br />
or maybe it's interacting with a game that you're building a bot for -- you <em>could</em><br />
build all that functionality into your component(s) -- because why prematurely abstract?<br />
But you could also build that functionality into a <em>Service</em>, or "just another<br />
class that your component will end up using", like this:</p>
<pre><code class="js language-js">class MyComponent {
  constructor() {
    this.api = new StarWarsApi();
  }
}

let myComponent = new MyComponent();</code></pre>
<p>This is a great first step! as the <code>StarWarsApi</code> can be tested by itself without<br />
needing to be tied to your component. <em>However</em>, your component has the opposite<br />
problem, it is <em>dependent</em> on the <code>StarWarsApi</code>, and there is no way to test<br />
the behaviors of <code>MyComponent</code> without using the real implementation of <code>StarWarsApi</code>.<br />
The solution to this is dependency injection, where the coupling between the<br />
specific implementation of <code>StarWarsApi</code> is reduced to just the interface<br />
(the list of methods that we care about), and during testing, we can swap out<br />
the <code>StarWarsApi</code> with a fake one that has all the same methods.</p>
<pre><code class="js language-js">class MyComponent {
  constructor(api) {
    this.api = api;
  }
}

let fakeApi = { /* fake stuff here */ }
let myComponent = new MyComponent(fakeApi);</code></pre>
<p>There is <em>a lot</em> of information on this topic, and I think <a href="https://stackoverflow.com/a/14301496">this StackOverflow Answer</a><br />
summarizes it well:</p>
<blockquote>
  <p>So, to cut a long story short: Dependency injection is one of two ways of how<br />
  to remove dependencies in your code. It is very useful for configuration<br />
  changes after compile-time, and it is a great thing for unit testing<br />
  (as it makes it very easy to inject stubs and / or mocks).</p>
</blockquote>
<p>Which reminds me of the whole point of software engineering and architecture in<br />
general: <em>to make testing easier.</em></p>
<p>If we do not learn from the mistakes of those before us and allow ourselves to make<br />
testing hard for both our coworkers as well as our future selves, we are doing<br />
our coworkers (and ourselves!) a disservice.</p>
<p>This could easily go on a tangent about the important and philosophy of testing<br />
and testing-driven architecture, but that's a topic for another time.</p>
<h2 id="how-does-dependency-injection-work-in-ember">How does Dependency Injection work in Ember?</h2>
<p>I think the best way to describe this is to first demonstrate how we would create<br />
our own dependency injection system from scratch.</p>
<p>This is a bottom-up approach, meaning that we start with the bare minimum, and the<br />
gradually add more behavior as we move forward. First, we'll need to define some<br />
terms and set goals, so we're on the same page:</p>
<p>Nomenclature:</p>
<ul>
<li>Service: a named bucket of state and/or behavior (usually a class instance);</li>
<li>Injection: the act of defining a reference to a Service</li>
<li>Container: the object that holds references to each Service</li>
</ul>
<p>Goals:</p>
<ol>
<li>A Service can be referenced from anywhere, regardless of where it is accessed</li>
<li>A Service is a <a href="https://en.wikipedia.org/wiki/Singleton_pattern">singleton</a></li>
<li>Services can reference each other (circular dependencies are valid)</li>
<li>Access to the global namespace is not allowed</li>
</ol>
<p>This could be considered an ancestor to dependency injection, where there exists<br />
a shared <code>container</code> object in the module scope, still allowing for us to<br />
achieve the first three goals.</p>
<pre><code class="ts language-ts">// app.js
let container = {};

function bootApp() {
  initializeServices();

  container.bot.begin();
}

class Bot {
  begin() {
    let nextMove = container.ai.getMove();

    container.ui.sendKeyPress(nextMove);
  }
}

function initalizeServices() {
  container.ai = new AI();
  container.bot = new Bot();
  container.ui = new UI();
}


bootApp();</code></pre>
<p>To see this code in action, view <a href="https://codesandbox.io/s/dependency-injection-1-19yqj">this CodeSandBox</a></p>
<p>In a multi-file environment we don't have access to the same module scope between files,</p>
<pre><code class="ts language-ts">// app.js
import Bot from './bot';
import AI from './ai';
import UI from './ui';

let container = {};

function bootApp() {
  initializeServices();

  container.bot.begin();
}

function initializeServices() {
  container.ai = new AI(container);
  container.bot = new Bot(container);
  container.ui = new UI(container);
}

// bot.js
export default class Bot {
  constructor(container) {
    this.container = container;
  }

  begin() {
    let nextMove = this.container.ai.getMove();

    this.container.ui.sendKeyPress(nextMove);
  }
}</code></pre>
<p>To see this code in action, view <a href="https://codesandbox.io/s/dependency-injection-2-b0qws">this CodeSandBox</a></p>
<p>However, as a framework or library developer, forcing users / application developers<br />
to remember to assign the container each time isn't very ergonomic.</p>
<pre><code class="ts language-ts">// app.js
// same as before

// service.js
export default class Service {
  constructor(container) {
    this.container = container;
  }
}

// bot.js
import Service from './service';

export default class Bot extends Service {
  begin() {
    let nextMove = this.container.ai.getMove();

    this.container.ui.sendKeyPress(nextMove);
  }
}</code></pre>
<p>This is a little better, we have abstracted away a bit of boilerplate, but there is still<br />
a "magic property", <code>container</code> -- this is generally where object oriented programming<br />
can get a negative reputation for -- a lack of <em>proper</em> or <em>incomplete</em> abstraction.</p>
<blockquote>
  <p><em>A bad abstraction is worse than no abstraction</em></p>
</blockquote>
<p>So, let's clean that up a bit using a <a href="https://babeljs.io/docs/en/babel-plugin-proposal-decorators">decorator</a></p>
<pre><code class="ts language-ts">// app.js
// same as before

// service.js
let CONTAINER = Symbol('container');

export default class Service {
  constructor(container) {
    // the container is now set on a symbol-property so that app-devs don't
    // directly access the container. We want app-devs to use the abstraction,
    // which we're aiming to be more ergonamic
    this[CONTAINER] = container;
  }
}

// this is a decorator, and would be used like `@injectService propertyName`
// where target is the class, name would be "propertyName", and descriptor is the
// property descriptor describing the existing "propertyName" on the class that is
// being decorated
//
// For more information on decorators, checkout the above linked decorator plugin
// for babel.
export function injectService(target, name, descriptor) {
  return {
    configurable: false,
    enumerable: true,
    get: function() {
      if (!this[CONTAINER]) {
        throw new Error(`${target.name} does not have a container. Did it extend from Service?`);
      }

      return this[CONTAINER][name];
    }
  }
}

// bot.js
import Service { injectService } from './service';

export default class Bot extends Service {
  @injectService ai;
  @injectService ui;

  begin() {
    let nextMove = this.ai.getMove();

    this.ui.sendKeyPress(nextMove);
  }
}</code></pre>
<p>To see this code in action, view <a href="https://codesandbox.io/s/dependency-injection-3-mum0p?file=/bot.js">this CodeSandBox</a></p>
<p>With this approach we can reference each service by name -- but we have a new problem now:<br />
<em>as a framework developer, how do we ensure that service properties match up to the service classes?</em></p>
<p>In the current implementation, we've been arbitrarily assigning values on the <code>container</code> object,<br />
<code>ui</code>, <code>ai</code>, and <code>bot</code>. Since this has been in user-space, we've always known what those properties<br />
are on the container.</p>
<p>This is where convention steps in.</p>
<p>As framework / library authors, we can say that services are required to be in the<br />
<code>services/</code> folder of your project.</p>
<pre><code class="ts language-ts">let container = {};

function bootApp() {
  initializeServices();

  container.bot.begin();
}

function initializeServices() {
  for (let [name, AppSpecificService] of detectedServices) {
   container[name]  = new AppSpecificService(container);
  }
}</code></pre>
<p>However, if you're familiar with module-based javascript, you'll noticed that <code>detectedServices</code><br />
needs to <em>somehow</em> be aware of the services in the <code>services/</code> folder and know their names.</p>
<p>This is where a CLI, at build-time, can help out our framework at run-time.</p>
<p>In Ember, this step is handled be the <a href="https://github.com/ember-cli/ember-resolver">ember-resolver</a><br />
which then defers to <a href="https://github.com/ember-cli/ember-resolver/blob/master/addon/resolvers/classic/index.js#L16">requirejs</a>,<br />
which <a href="https://requirejs.org/docs/api.html#define">defines modules</a> in the <a href="https://requirejs.org/docs/whyamd.html#namedmodules">AMD</a><br />
format -- which, for now, we don't need to worry about.</p>
<p>For demonstration purposes, we'll "say" that our bundler and CLI are configured<br />
together to produce a map of relative file paths to modules:</p>
<pre><code class="ts language-ts">let containerRegistry = {
  'services/bot': await import('./services/bot'),
  'services/ai': await import('./services/ai'),
  'services/ui': await import('./services/ui'),
}</code></pre>
<p>so then our <code>app.js</code> may look like this:</p>
<pre><code class="ts language-ts">let knownServices = Object.entries(containerRegistry);
let container = {};

function bootApp() {
  initializeServices();

  container.bot.begin();
}

function initializeServices() {
  for (let [fullName, ServiceModule] of knownServices) {
    let name = fullName.replace('services/', '');
    let DefaultExport = ServiceModule.default;

    container[name]  = new DefaultExport(container);
  }
}</code></pre>
<p>So now in our documentation, we can write that whatever the file name of the service is<br />
will be the name of the property pointing to an instance of that service within<br />
the <code>container</code>.</p>
<p>Now, what if we wanted our services to be lazily instantiated, so that we don't negatively<br />
impact the <em>time to interactive</em> benchmark if we don't have to?</p>
<p>So far our <code>container</code> has been a plain old object. We can utilize a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a></p>
<pre><code class="ts language-ts">let knownServices = Object.entries(containerRegistry);
let registry = {};

let container = new Proxy(registry, {
  get: function(target, propertyName) {
    if (target[propertyName]) {
      return target[propertyName];
    }

    let FoundService = lookupService(propertyName);

    target[propertyName] = new FoundService(container);

    return target[propertyName];
  }
});

function lookupService(serviceName) {
  let fullPath = `services/${serviceName}`;
  let doesServiceExist = Object.keys(containerRegistry).includes(fullPath);

  if (!doesServiceExist) {
    throw new Error(`The Service, ${serviceName}, was not found.`);
  }

  // We establish a convention that the service is defined on the default export
  return containerRegistry[fullPath].default;
}

function bootApp() {
  // initialization now happens on-demand
  container.bot.begin();
}</code></pre>
<p>To see the final implementation, view <a href="https://codesandbox.io/s/dependency-injection-4-5fjzg">this CodeSandBox</a></p>
<h2 id="what-does-ember-do-behind-the-scenes">What does Ember do behind the scenes?</h2>
<p>Ember abstracts nearly all of the above from you and provides conventions for<br />
building out the map of service names to service instances, accessing those<br />
services, and creating <em>any</em> container aware-object.</p>
<p>The most important thing to know about the container, is that it'll<br />
provide the contained, known internally-to-ember as the "owner", as<br />
the first argument to each of your classes.</p>
<p>So, if you want to have your own "kind" of object, maybe it's a bunch of custom<br />
objects that interact with something external, such as an API, or a Canvas, or WebGL,<br />
or .. really anything!, it's possible to <em>register</em> your objects with Ember's<br />
container.</p>
<p>Ember does this internally for Services, Routes, Controllers, Components, Helpers,<br />
and Modifiers, but to do what ember is doing, have this somewhere in your app</p>
<pre><code class="js language-js">// maybe in a Route's beforeModel hook
let owner = getOwner(this);
owner.register(
  /*
    full name in the format:
    namespace:name
  */
  'webgl:renderer',
  /* class */
  Renderer
);</code></pre>
<p>Now, how would you access that from your component? It's not a service, so the<br />
service decorator wouldn't work. First, let's look at what the service decorator <em>does</em> look like</p>
<pre><code class="js language-js">// abridged version of the @service decorator
//
//
// NOTE: ember convention is:
//   import { inject as service } from '@ember/service';
export function inject(target, name, descriptor) {
  return {
    configurable: false,
    enumerable: true,
    get: function() {
      let owner = getOwner(this);

      return owner.lookup(`service:${name}`);
    }
  }
}</code></pre>
<p>So that way, when you have <code>@service api</code>, the <em>namespace</em> gets prepending for<br />
you, and the <code>service:api</code> <em>full name</em> is looked up in the container.</p>
<p>Knowing the above, we can make our own decorator so that we may access the our<br />
"foo" singleton</p>
<pre><code class="js language-js">export function webgl(target, name, descriptor) {
  return {
    configurable: false,
    enumerable: true,
    get: function() {
      let owner = getOwner(this);

      return owner.lookup(`webgl:${name}`);
    }
  }
}</code></pre>
<p>So then <em>anywhere</em> in our app, we could have a component with the following:</p>
<pre><code class="js language-js">class MyComponent extends Component {
  @webgl renderer;
}</code></pre>
<h2 id="thats-all-folks">"That's all, folks!"</h2>
<p>Once I realized the implementation of ember's dependency injection, it felt<br />
simple. It's pretty much a <em>global store</em> where instances of classes are<br />
stored on that <em>global store</em> and referenced from other places within your app.<br />
If something here <em>doesn't</em> feel simple, let me know!, and hopefully I can tweak<br />
this blog post until it does feel simple.</p>
<p>I like the pattern a lot, because it avoids the need to explicitly pass references<br />
to every object you want to use throughout your entire app. Instead, Ember abstracts<br />
away the passing of the container object to all objects created through that container<br />
(mostly components and services, but custom classes can be used as well).</p>
<h2 id="disclaimers">Disclaimers</h2>
<p>Dependency injection can be a big topic and have a lot of features implemented.<br />
This demonstration has narrow scope and is not intended to be a "fully featured"<br />
dependency injection implementation.</p>
<h2 id="about">About</h2>
<p>Professionally, I had my start to frontend development in React, and at the time<br />
there was really only Redux and MobX for state management -- but I only had the<br />
privilege of working with Redux and eventually React's Context Provider/Consumer<br />
pattern. There <em>is</em> a little bit of overlap between React's Contexts and Ember's<br />
Services, but they differ in fundamental ways -- which could be a topic for<br />
another time.</p>
<p>Now that I'm getting paid to work with Ember almost every day I've only<br />
gotten more excited about the programming patterns introduced by the framework and<br />
am eager to share them with the world.</p>
<hr />
<p><em>This was inspired from some conversations on Twitter as well as trying not
to use a web framework for building an
<a href="https://github.com/NullVoxPopuli/doctor-who-thirteen-game-ai/blob/bc09c823abe89894cf7607aaa1820c348b900c10/ai.js#L5">Artificatial Intelligence to play a game</a></em></p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/tc39/proposal-decorators">TC39 Decorator Proposal</a></li>
<li><a href="https://guides.emberjs.com/release/applications/dependency-injection/">Ember Documentation on Dependency Injection</a></li>
</ul>]]></description><link>https://nullvoxpopuli.com/2020-08-08-how-does-di-work</link><guid isPermaLink="true">https://nullvoxpopuli.com/2020-08-08-how-does-di-work</guid><pubDate>Sat, 08 Aug 2020 17:04:01 GMT</pubDate></item><item><title><![CDATA[Size of all node_modules]]></title><description><![CDATA[<h1 id="node_modules">node_modules</h1>
<p>Ever been curious about how much space your <code>node_modules</code> directories are taking up across every project on your file system?</p>
<p>From the root of your project directory, run:</p>
<pre><code class="bash language-bash">find . -name "node_modules" -type d -exec du -bc {} + \
  | grep total$ \
  | cut -f1 \
  | awk '{ total += $1 }; END { print total }' \
  | numfmt --to=iec</code></pre>
<p>Explanation: <a href="https://unix.stackexchange.com/a/589060">https://unix.stackexchange.com/a/589060</a></p>
<p>This will search for every <code>node_modules</code> directory, find the size, and add all the sizes together before showing you a number.</p>
<p>For me (on an overworked computer), this was:</p>
<pre><code class="bash language-bash">40G
&gt;&gt; took 6m37s</code></pre>]]></description><link>https://nullvoxpopuli.com/2021-07-21-size-of-all-node-modules</link><guid isPermaLink="true">https://nullvoxpopuli.com/2021-07-21-size-of-all-node-modules</guid><pubDate>Wed, 21 Jul 2021 19:45:43 GMT</pubDate></item><item><title><![CDATA[Ember + WebStorm Editor Config]]></title><description><![CDATA[<h1 id="ember--webstorm">Ember + WebStorm</h1>
<p>I don't use WebStorm, but common feedback I get from people who use WebStorm is that they don't have all the features and niceties that you'd get with neovim or VSCode even.</p>
<p>The main features missing from WebStorm (by default) are:</p>
<ul>
<li><code>hbs</code> tagged template literal highlighting in JavaScript and TypeScript. This is useful for tests, single-file components, as well as multi-component files.</li>
<li><a href="https://github.com/lifeart/ember-language-server">Ember Language Server</a> support. This provides a bunch of hints, go-to-definition from templates, and a bunch of nice features.</li>
<li>Integration with <a href="https://github.com/ember-template-lint/ember-template-lint">ember-template-lint</a>. This is normally provided by the language server… but without the language server, there are no lint hints.</li>
<li>Correct template parsing. WebStorm uses <code>handlebars</code> parsing, which isn't correct for ember and glimmer projects as the language of templates used is only handlebars-esque and is actually much richer than handlebars while also eliminating many features of handlebars that people don't like (like implicit context scoping). It also seems that components and named blocks are incorrectly identified as custom html tags, giving you a yellow squiggly under them.<br />
<img src="/images/webstorm/menu-component.png" alt="screenshot of WebStorm incorrectly identifying components, and yielded values" /></li>
</ul>
<h2 id="hbs-template-string-higlighting"><code>hbs</code> template string higlighting</h2>
<p>So far, I've been able to solve the <code>hbs</code> tagged template literal highlighting in JavaScript and Typescript, and here is how you can set that up yourself,</p>
<p>In WebStorm 2021.2.2,</p>
<ol>
<li><p>Open the File Menu &gt; Settings</p></li>
<li><p>Search for "Injection"</p></li>
<li><p>Click on "Language Injections"</p></li>
<li><p>Click on "HTML in JS strings"</p></li>
<li><p>Click the "Duplicate" button</p></li>
<li><p>Double Click on your duplicated "HTML in JS strings"</p></li>
<li><p>Change the settings to look like this</p>
<ul>
<li>Name: <code>HBS in JS strings</code></li>
<li>ID: <code>Handlebars</code></li>
<li>Places Patterns: <code>+ taggedString("hbs")</code></li></ul>
<p><img src="/images/webstorm/hbs-injection-settings.png" alt="picture of above-mentioned settings" /></p></li>
<li><p>End Result gets you <code>hbs</code> tagged template literal highlighting in both tests and non-test code:<br />
<img src="/images/webstorm/hbs-highlighting.png" alt="proof of hbs highlighting in typescript" /></p></li>
</ol>
<h2 id="other-editor-experiences">Other editor experiences</h2>
<p>There are two plugins at the moment for working with Ember in IntelliJ editors:</p>
<ul>
<li><p><a href="https://plugins.jetbrains.com/plugin/8049-ember-js">Ember.JS</a></p></li>
<li><p>and <a href="https://plugins.jetbrains.com/plugin/15499-ember-experimental-js">Ember Experimental.JS</a></p>
<p>According to the author, this adds (in addition to the previous plugin):</p>
<ul>
<li>Handlebars references for tags/mustache paths and tag attributes</li>
<li>Handlebars autocompletion for tags and mustache paths</li>
<li>Handlebars parameter hints for helpers/modifiers and components</li>
<li>Handlebars renaming for mustache ids and html tags</li></ul>
<p>For all the other problems, I'll be periodically checking in with the folks who work on the Ember plugins for the IntelliJ family of editors, and see if it's feasible to get things moving sooner or later (scale of timeline unknown, obvs).</p></li>
</ul>
<p><em>Note that at the time of writing, I only have these plugins installed</em></p>
<p><img src="/images/webstorm/downloaded-plugins.png" alt="Downloaded Plugins, listed below" /></p>
<ul>
<li><a href="https://plugins.jetbrains.com/plugin/8049-ember-js">Ember.JS</a></li>
<li><a href="https://plugins.jetbrains.com/plugin/11938-one-dark-theme">One Dark theme</a></li>
<li><a href="https://plugins.jetbrains.com/plugin/13883-rider-ui-theme-pack">Rider UI Theme Pack</a></li>
</ul>]]></description><link>https://nullvoxpopuli.com/2021-10-02-ember-webstorm-editor-config</link><guid isPermaLink="true">https://nullvoxpopuli.com/2021-10-02-ember-webstorm-editor-config</guid><pubDate>Sat, 02 Oct 2021 16:09:38 GMT</pubDate></item><item><title><![CDATA[Ember Integration Cookbook]]></title><description><![CDATA[<p>Gonna try to keep this list up to date best I can</p>
<p>MSW</p>
<ul>
<li><a href="https://github.com/NullVoxPopuli/ember-data-resources/blob/main/tests/unit/find-all-test.ts#L16">Using MSW for tests only</a></li>
<li><a href="https://github.com/NullVoxPopuli/ember-msw-development/commits/main">Using MSW for development</a></li>
</ul>
<p>CSS / Styles</p>
<ul>
<li><a href="https://discuss.emberjs.com/t/ember-modern-css/19614">tailwind, postcss, embroider</a></li>
</ul>
<p>Component/Template Demos / Concepts</p>
<ul>
<li><a href="https://discuss.emberjs.com/t/collection-of-strict-mode-template-demos-of-various-concepts/19637">In this other post</a><ul>
<li>Effects</li>
<li>Loading Remote Data</li>
<li>Forms / Inputs</li>
<li><code>Resources</code></li>
<li>Modifiers via the <a href="https://github.com/ember-modifier/ember-modifier">ember-modifier</a> README -- probably the most comprehensive introduction to modifiers atm.</li></ul></li>
</ul>
<p>Patterns / Concepts</p>
<ul>
<li><a href="https://www.youtube.com/watch?app=desktop&v=Mt7v-VbFjxk&feature=emb_title">"Keep it Local"</a> by <a href="https://github.com/chriskrycho">@chriskrycho</a> / EmberConf 2021 (Video)</li>
<li>re-thinking lifecycles<ul>
<li><a href="https://nullvoxpopuli.com/avoiding-lifecycle">avoiding <code>@ember/render-modifiers</code></a></li></ul></li>
<li><a href="https://stackblitz.com/edit/github-qivg2a">Authenticated Routes</a> </li>
</ul>
<p>Starter Repos / Apps (needs READMEs / instructions for tooling setup)</p>
<ul>
<li><a href="https://github.com/NullVoxPopuli/polaris-starter">Polaris</a></li>
<li><a href="https://github.com/NullVoxPopuli/polaris-toucan-starter">Polaris + Toucan</a> (Polaris + tailwind, using the Toucan design system tailwind preset)</li>
<li><a href="https://github.com/lifeart/demo-ember-vite">Vite</a> - lightning fast boot and rebuild times, at the cost of compatibility with existing projects</li>
<li><a href="https://github.com/tdwesten/ember-tauri-starter">Tauri</a> - example of how to use <a href="https://tauri.app/">Tauri</a> with Ember.</li>
</ul>
<p>Custom Blueprints</p>
<ul>
<li><a href="https://github.com/NullVoxPopuli/vitest-blueprint">vitest</a> - since vitest only runs in node, this is only useful for testing non-browser things</li>
<li><a href="https://github.com/embroider-build/addon-blueprint">complex blueprint</a> - the v2 addon blueprint -- lots of flags / behaviors, generates multiple packages/projects</li>
</ul>
<p>Example Apps</p>
<ul>
<li><a href="https://github.com/NullVoxPopuli/ember-todomvc-tutorial">Octane: TodoMVC</a> - deployed <a href="https://nullvoxpopuli.github.io/ember-todomvc-tutorial/">here</a> - guide <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">on MDN</a></li>
</ul>
<p><em>Originally posted <a href="https://discuss.emberjs.com/t/my-cookbook-for-various-emberjs-things/19679">here</a> on the <a href="https://discuss.emberjs.com/">Ember Forums</a> -- there is some additional discussion there as well.</em></p>]]></description><link>https://nullvoxpopuli.com/2022-08-18-ember-integration-cookbook</link><guid isPermaLink="true">https://nullvoxpopuli.com/2022-08-18-ember-integration-cookbook</guid><pubDate>Tue, 06 Sep 2022 00:35:22 GMT</pubDate></item><item><title><![CDATA[Collection of Ember.JS' strict mode / <template> demos of various concepts]]></title><description><![CDATA[<p>I've been writing a lot of demos lately in the REPL I've been working on, <a href="https://limber.glimdown.com">limber.glimdown.com</a></p>
<p>This post will be a collection of demos I've used in response to questions from various community members -- for the purpose of finding these again easily, and maybe other folks will f ind them useful as well.</p>
<h2 id="loading-remote-data">Loading Remote Data</h2>
<ul>
<li>shows how to use loading state</li>
<li>keeps the UI stable while new data is loaded / refresh</li>
</ul>
<p>Demos:</p>
<ul>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20RemoteData%0A%0A`RemoteData`%20is%20a%20utility%20`Resource`%20from%20[ember-resources][gh-resources]%0Athat%20provides%20an%20easy%20way%20to%20interact%20with%20[`fetch`][mdn-fetch]%0Awith%20a%20pre-wired%20[`AbortController`][mdn-AbortController].%0A%0AIn%20this%20example%2C%20the%20fetching%20of%20data%20from%20the%20[StarWars%20API][swapi]%20occurs%0Aautomatically%20based%20on%20changes%20to%20the%20URL.%0AYou%20may%20change%20the%20`id`%20of%20the%20Person%20to%20fetch%20from%20the%20StarWars%20API.%0A%0A```gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20{%20tracked%20}%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20{%20on%20}%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aimport%20{%20RemoteData%20}%20from%20%27ember-resources%2Futil%2Fremote-data%27%3B%0A%0Aconst%20urlFor%20%3D%20(id)%20%3D%3E%20`https%3A%2F%2Fswapi.dev%2Fapi%2Fpeople%2F%24{id}`%3B%0A%0Alet%20previous%3B%0Aconst%20keepLatest%20%3D%20(data)%20%3D%3E%20previous%20%3D%20data%20||%20previous%3B%0A%0Aconst%20Person%20%3D%20%3Ctemplate%3E%0A%20%20{{%23let%20(RemoteData%20(urlFor%20%40id))%20as%20|request|}}%0A%20%20%20%20{{keepLatest%20request.value.name}}%0A%0A%20%20%20%20{{%23if%20request.isLoading}}%20…%20loading%20{{%40id}}%20…%20{{%2Fif}}%0A%20%20{{%2Flet}}%0A%3C%2Ftemplate%3E%3B%0A%0Aexport%20default%20class%20Demo%20extends%20Component%20{%0A%20%20%40tracked%20id%20%3D%2051%3B%0A%20%20updateId%20%3D%20(event)%20%3D%3E%20this.id%20%3D%20event.target.value%3B%0A%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%3Cdiv%20class%3D%22border%20p-4%20grid%20gap-4%22%3E%0A%20%20%20%20%20%20%20%20%3CPerson%20%40id%3D{{this.id}}%20%2F%3E%0A%0A%20%20%20%20%20%20%20%20%3Clabel%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20Person%20ID%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20type%3D%27number%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class%3D%27border%20px-3%20py-2%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20value%3D{{this.id}}%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20{{on%20%27input%27%20this.updateId}}%3E%0A%20%20%20%20%20%20%20%20%3C%2Flabel%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%20%20%3C%2Ftemplate%3E%0A}%0A```%0A%0ADocs%20for%20`RemoteData`%20can%20[be%20found%20here][docs-remote-data].%0AInformation%20about%20how%20Resources%20fit%20in%20to%20the%20next%20edition%20of%20Ember%20can%20be%20[found%20here][polaris-reactivity]%0A%0A%0A%0A[gh-resources]%3A%20https%3A%2F%2Fgithub.com%2Fnullvoxpopuli%2Fember-resources%0A[mdn-fetch]%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFetch_API%2FUsing_Fetch%0A[mdn-AbortController]%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FAbortController%0A[docs-remote-data]%3A%20https%3A%2F%2Fember-resources.pages.dev%2Fmodules%2Futil_remote_data%0A[polaris-reactivity]%3A%20https%3A%2F%2Fwycats.github.io%2Fpolaris-sketchwork%2Freactivity.html%0A[swapi]%3A%20https%3A%2F%2Fswapi.dev%2F%0A">using Resources</a> - uses resources in the template</li>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20RemoteData%0A%0A%60RemoteData%60%20is%20a%20utility%20%60Resource%60%20from%20%5Bember-resources%5D%5Bgh-resources%5D%0Athat%20provides%20an%20easy%20way%20to%20interact%20with%20%5B%60fetch%60%5D%5Bmdn-fetch%5D%0Awith%20a%20pre-wired%20%5B%60AbortController%60%5D%5Bmdn-AbortController%5D.%0A%0AIn%20this%20example%2C%20the%20fetching%20of%20data%20from%20the%20%5BStarWars%20API%5D%5Bswapi%5D%20occurs%0Aautomatically%20based%20on%20changes%20to%20the%20URL.%0AYou%20may%20change%20the%20%60id%60%20of%20the%20Person%20to%20fetch%20from%20the%20StarWars%20API.%0A%0A%60%60%60gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aimport%20%7B%20use%2C%20resource%20%7D%20from%20%27ember-resources%27%3B%0Aimport%20%7B%20RemoteData%2C%20remoteData%20%7D%20from%20%27ember-resources%2Futil%2Fremote-data%27%3B%0Aimport%20%7B%20keepLatest%20%7D%20from%20%27ember-resources%2Futil%2Fkeep-latest%27%3B%0A%0Aconst%20isEmpty%20%3D%20(x)%20%3D%3E%20!x%20%7C%7C%20x%3F.length%20%3D%3D%3D%200%3B%0Aconst%20urlFor%20%3D%20(id)%20%3D%3E%20%60https%3A%2F%2Fswapi.dev%2Fapi%2Fpeople%2F%24%7Bid%7D%60%3B%0A%0Aexport%20default%20class%20Demo%20extends%20Component%20%7B%0A%20%20%40tracked%20id%20%3D%2051%3B%0A%20%20updateId%20%3D%20(event)%20%3D%3E%20this.id%20%3D%20event.target.value%3B%0A%0A%20%20%40use%20request%20%3D%20RemoteData(()%20%3D%3E%20urlFor(this.id))%3B%0A%20%20%40use%20latest%20%3D%20keepLatest(%7B%20%0A%20%20%20%20value%3A%20()%20%3D%3E%20this.request.value%2C%0A%20%20%20%20when%3A%20()%20%3D%3E%20this.request.isLoading%2C%0A%20%20%7D)%3B%0A%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%3Cdiv%20class%3D%22border%20p-4%20grid%20gap-4%22%3E%0A%20%20%20%20%20%20%7B%7Bthis.latest.name%7D%7D%0A%0A%20%20%20%20%20%20%7B%7B%23if%20this.request.isLoading%7D%7D%0A%20%20%20%20%20%20%20%20…%20loading%20%7B%7Bthis.id%7D%7D%20…%0A%20%20%20%20%20%20%7B%7B%2Fif%7D%7D%0A%0A%20%20%20%20%20%20%20%20%3Clabel%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20Person%20ID%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20type%3D%27number%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class%3D%27border%20px-3%20py-2%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20value%3D%7B%7Bthis.id%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bon%20%27input%27%20this.updateId%7D%7D%3E%0A%20%20%20%20%20%20%20%20%3C%2Flabel%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%20%20%3C%2Ftemplate%3E%0A%7D%0A%60%60%60%0A%0ADocs%20for%20%60RemoteData%60%20can%20%5Bbe%20found%20here%5D%5Bdocs-remote-data%5D.%0AInformation%20about%20how%20Resources%20fit%20in%20to%20the%20next%20edition%20of%20Ember%20can%20be%20%5Bfound%20here%5D%5Bpolaris-reactivity%5D%0A%0A%0A%0A%5Bgh-resources%5D%3A%20https%3A%2F%2Fgithub.com%2Fnullvoxpopuli%2Fember-resources%0A%5Bmdn-fetch%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFetch_API%2FUsing_Fetch%0A%5Bmdn-AbortController%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FAbortController%0A%5Bdocs-remote-data%5D%3A%20https%3A%2F%2Fember-resources.pages.dev%2Fmodules%2Futil_remote_data%0A%5Bpolaris-reactivity%5D%3A%20https%3A%2F%2Fwycats.github.io%2Fpolaris-sketchwork%2Freactivity.html%0A%5Bswapi%5D%3A%20https%3A%2F%2Fswapi.dev%2F%0A">using a <code>keepLatest</code> Resource</a> - uses resources in JS - and <code>keepLatest</code> from <code>ember-resources</code></li>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20RemoteData%0A%0A%60RemoteData%60%20is%20a%20utility%20%60Resource%60%20from%20%5Bember-resources%5D%5Bgh-resources%5D%0Athat%20provides%20an%20easy%20way%20to%20interact%20with%20%5B%60fetch%60%5D%5Bmdn-fetch%5D%0Awith%20a%20pre-wired%20%5B%60AbortController%60%5D%5Bmdn-AbortController%5D.%0A%0AIn%20this%20example%2C%20the%20fetching%20of%20data%20from%20the%20%5BStarWars%20API%5D%5Bswapi%5D%20occurs%0Aautomatically%20based%20on%20changes%20to%20the%20URL.%0AYou%20may%20change%20the%20%60id%60%20of%20the%20Person%20to%20fetch%20from%20the%20StarWars%20API.%0A%0A%60%60%60gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aimport%20%7B%20use%2C%20resource%20%7D%20from%20%27ember-resources%27%3B%0Aimport%20%7B%20RemoteData%2C%20remoteData%20%7D%20from%20%27ember-resources%2Futil%2Fremote-data%27%3B%0A%0Aconst%20isEmpty%20%3D%20(x)%20%3D%3E%20!x%20%7C%7C%20x%3F.length%20%3D%3D%3D%200%3B%0Aconst%20urlFor%20%3D%20(id)%20%3D%3E%20%60https%3A%2F%2Fswapi.dev%2Fapi%2Fpeople%2F%24%7Bid%7D%60%3B%0A%0Aconst%20keepLatest%20%3D%20(%7B%20until%2C%20value%3A%20valueFn%20%7D)%20%3D%3E%20resource(()%20%3D%3E%20%7B%0A%20%20let%20previous%3B%0A%20%20%20%20%0A%20%20return%20()%20%3D%3E%20%7B%0A%20%20%20%20let%20value%20%3D%20valueFn()%3B%0A%20%20%20%20if%20(until())%20%7B%0A%20%20%20%20%20%20return%20previous%20%3D%20isEmpty(value)%20%3F%20previous%20%3A%20value%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20return%20previous%20%3D%20value%3B%0A%20%20%7D%3B%0A%7D)%3B%0A%0A%0Aexport%20default%20class%20Demo%20extends%20Component%20%7B%0A%20%20%40tracked%20id%20%3D%2051%3B%0A%20%20updateId%20%3D%20(event)%20%3D%3E%20this.id%20%3D%20event.target.value%3B%0A%0A%20%20%40use%20request%20%3D%20resource(hooks%20%3D%3E%20remoteData(hooks%2C%20urlFor(this.id)))%3B%0A%0A%20%20%40use%20withLatest%20%3D%20keepLatest(%7B%20%0A%20%20%20%20until%3A%20()%20%3D%3E%20this.request.isLoading%2C%0A%20%20%20%20value%3A%20()%20%3D%3E%20this.request.value%2C%0A%20%20%7D)%3B%0A%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%3Cdiv%20class%3D%22border%20p-4%20grid%20gap-4%22%3E%0A%20%20%20%20%20%20%7B%7Bthis.withLatest.name%7D%7D%0A%0A%20%20%20%20%20%20%7B%7B%23if%20this.request.isLoading%7D%7D%0A%20%20%20%20%20%20%20%20…%20loading%20%7B%7Bthis.id%7D%7D%20…%0A%20%20%20%20%20%20%7B%7B%2Fif%7D%7D%0A%0A%20%20%20%20%20%20%20%20%3Clabel%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20Person%20ID%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20type%3D%27number%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class%3D%27border%20px-3%20py-2%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20value%3D%7B%7Bthis.id%7D%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bon%20%27input%27%20this.updateId%7D%7D%3E%0A%20%20%20%20%20%20%20%20%3C%2Flabel%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%20%20%3C%2Ftemplate%3E%0A%7D%0A%60%60%60%0A%0ADocs%20for%20%60RemoteData%60%20can%20%5Bbe%20found%20here%5D%5Bdocs-remote-data%5D.%0AInformation%20about%20how%20Resources%20fit%20in%20to%20the%20next%20edition%20of%20Ember%20can%20be%20%5Bfound%20here%5D%5Bpolaris-reactivity%5D%0A%0A%0A%0A%5Bgh-resources%5D%3A%20https%3A%2F%2Fgithub.com%2Fnullvoxpopuli%2Fember-resources%0A%5Bmdn-fetch%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFetch_API%2FUsing_Fetch%0A%5Bmdn-AbortController%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FAbortController%0A%5Bdocs-remote-data%5D%3A%20https%3A%2F%2Fember-resources.pages.dev%2Fmodules%2Futil_remote_data%0A%5Bpolaris-reactivity%5D%3A%20https%3A%2F%2Fwycats.github.io%2Fpolaris-sketchwork%2Freactivity.html%0A%5Bswapi%5D%3A%20https%3A%2F%2Fswapi.dev%2F%0A">exposing the implementation of <code>keepLatest</code></a> - uses resources in JS</li>
</ul>
<h2 id="forms--inputs">Forms / Inputs</h2>
<ul>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Forms%0A%0A%23%23%23%20Using%20_The%20Platform_%0A%0AVanilla%20JavaScript%20has%20everything%20we%20need%20to%20handle%20form%20data%2C%20de-sync%20it%20from%20our%20source%20data%20and%20collect%20all%20user%20input%20upon%20submission.%0A%0AOther%20abstractions%2C%20such%20as%20the%20%22changeset%22%20concept%20contain%20a%20lot%20of%20this%20functionality%20and%20have%20additional%20utilities%20such%20as%20rollback%2C%20snapshots%2C%20forking%2C%20etc%2C%20but%20that%20is%20a%20topic%20for%20another%20demo.%0A%0AIn%20the%20form%20below%2C%20we%20create%20a%20Vanilla%E2%84%A2%20%5BHTML%20form%5D%5B2%5D%2C%20and%20only%20add%20%22Ember%22%20code%20for%20handling%20the%20form%20submission%20and%20field%20inputs.%20By%20default%2C%20form%20submissions%20will%20cause%20a%20page%20reload%2C%20so%20in%20a%20single-page-app%2C%20we%20need%20to%20prevent%20that%20default%20behavior.%0A%0AUsing%20the%20native%20API%2C%20%5BFormData%5D%5B1%5D%2C%20we%20can%20gather%20the%20user%20inputs%20when%20the%20user%20presses%20the%20submit%20button.%0A%0A%60%60%60gjs%20live%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0A%0Alet%20state%20%3D%20new%20(class%20%7B%0A%20%20%40tracked%20current%3B%0A%7D)()%3B%0A%0Aconst%20handleInput%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20let%20formData%20%3D%20new%20FormData(event.currentTarget)%3B%0A%20%20let%20data%20%3D%20Object.fromEntries(formData.entries())%3B%0A%0A%20%20state.current%20%3D%20JSON.stringify(data%2C%20null%2C%202)%3B%0A%7D%3B%0A%0Aconst%20handleSubmit%20%3D%20(%20event)%20%3D%3E%20%7B%0A%20%20event.preventDefault()%3B%0A%20%20handleInput(event)%3B%0A%7D%3B%0A%0A%3Ctemplate%3E%0A%20%20%3Cform%20%0A%20%20%20%20%7B%7Bon%20%27input%27%20handleInput%7D%7D%20%0A%20%20%20%20%7B%7Bon%20%27submit%27%20handleSubmit%7D%7D%0A%20%20%20%20class%3D%22grid%20gap-2%22%20%0A%20%20%20%20style%3D%22max-width%3A%20300px%22%0A%20%20%3E%0A%20%20%20%20%3Clabel%3E%20First%20Name%0A%20%20%20%20%20%20%3Cinput%20name%3D%27firstName%27%3E%0A%20%20%20%20%3C%2Flabel%3E%0A%0A%20%20%20%20%3Clabel%3E%20Favorite%20Date%0A%20%20%20%20%20%20%3Cinput%20type%3D%27date%27%20name%3D%27favoriteDate%27%3E%0A%20%20%20%20%3C%2Flabel%3E%0A%0A%20%20%20%20%3Cbutton%20type%3D%27submit%27%3ESubmit%3C%2Fbutton%3E%0A%20%20%3C%2Fform%3E%0A%0A%20%20%3Cbr%3E%3Cbr%3E%0A%0A%20%20FormData%3A%0A%20%20%3Cpre%3E%7B%7Bstate.current%7D%7D%3C%2Fpre%3E%0A%0A%20%20%3Cstyle%3E%0A%20%20%20%20input%20%7B%20border%3A%201px%20solid%3B%20%7D%0A%20%20%3C%2Fstyle%3E%0A%3C%2Ftemplate%3E%0A%60%60%60%0A%0A%3Chr%3E%0A%0A%23%23%23%20%22Platform%22%20References%0A%20-%20%5B%60%3Cform%3E%60%20on%20MDN%5D%5B2%5D%0A%20-%20%5B%60FormData%60%20on%20MDN%5D%5B1%5D%0A%0A%23%23%23%20Ember%20References%0A%20-%20%5Bthe%20%60on%60%20modifier%5D%5B3%5D%0A%0A%0A%5B1%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFormData%0A%5B2%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTML%2FElement%2Fform%0A%5B3%5D%3A%20https%3A%2F%2Fguides.emberjs.com%2Frelease%2Fcomponents%2Fcomponent-state-and-actions%2F%23toc_html-modifiers-and-actions%0A">automatic binding of inputs</a> (better than both two-way binding and one-way binding)</li>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Checkboxes%20in%20Ember%0A%0A%23%23%20Controlled%20Checkbox%0A%0A%60%60%60gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aexport%20default%20class%20Demo%20extends%20Component%20%7B%0A%20%20%40tracked%20value%3B%0A%0A%20%20update%20%3D%20(event)%20%3D%3E%20this.value%20%3D%20event.target.checked%3B%0A%20%20%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%7B%7Bthis.value%7D%7D%3Cbr%3E%0A%20%20%20%20%3Clabel%3E%20%0A%20%20%20%20%20%20the%20checkbox%0A%20%20%0A%20%20%20%20%20%20%3Cinput%20%0A%20%20%20%20%20%20%20%20type%3D%22checkbox%22%0A%20%20%20%20%20%20%20%20checked%3D%7B%7Bthis.value%7D%7D%20%0A%20%20%20%20%20%20%20%20class%3D%22border%22%0A%20%20%20%20%20%20%20%20%7B%7Bon%20%27change%27%20this.update%7D%7D%20%0A%20%20%20%20%20%20%2F%3E%0A%20%20%20%20%3C%2Flabel%3E%0A%20%20%3C%2Ftemplate%3E%0A%7D%0A%60%60%60%0A%0A%23%23%20Automatic%20binding%20using%20a%20%60%3Cform%3E%60%0A%0A%60%60%60gjs%20live%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20cell%20%7D%20from%20%27ember-resources%27%3B%0A%0Alet%20state%20%3D%20cell()%3B%0A%0Aconst%20handleInput%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20let%20formData%20%3D%20new%20FormData(event.currentTarget)%3B%0A%20%20let%20data%20%3D%20Object.fromEntries(formData.entries())%3B%0A%0A%20%20state.current%20%3D%20JSON.stringify(data%2C%20null%2C%202)%3B%0A%7D%3B%0A%0Aconst%20handleSubmit%20%3D%20(%20event)%20%3D%3E%20%7B%0A%20%20event.preventDefault()%3B%0A%20%20handleInput(event)%3B%0A%7D%3B%0A%0A%3Ctemplate%3E%0A%20%20%3Cform%20%0A%20%20%20%20%7B%7Bon%20%27input%27%20handleInput%7D%7D%20%0A%20%20%20%20%7B%7Bon%20%27submit%27%20handleSubmit%7D%7D%0A%20%20%20%20class%3D%22grid%20gap-2%22%20%0A%20%20%20%20style%3D%22max-width%3A%20300px%22%0A%20%20%3E%0A%20%20%20%20%3Clabel%3E%20isChecked%0A%20%20%20%20%20%20%3Cinput%20type%3D%22checkbox%22%20value%3D%22totally%20checked%22%20name%3D%27isChecked%27%3E%0A%20%20%20%20%3C%2Flabel%3E%0A%0A%20%20%20%20%3Cbutton%20type%3D%27submit%27%3ESubmit%3C%2Fbutton%3E%0A%20%20%3C%2Fform%3E%0A%0A%20%20%3Cbr%3E%3Cbr%3E%0A%0A%20%20FormData%3A%0A%20%20%3Cpre%3E%7B%7Bstate.current%7D%7D%3C%2Fpre%3E%0A%0A%3C%2Ftemplate%3E%0A%60%60%60">checkboxes</a><ul>
<li>controlled input (explicit binding)</li>
<li>uncontrolled input (automatic binding)</li></ul></li>
</ul>
<h2 id="polling">Polling</h2>
<ul>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Polling%20data%0A%0AA%20common%20thing%20folks%20ask%20is%20to%20re-call%20%2F%20re-run%20the%20%5Broute%27s%20model%20hook%5D%5Broute-model%5D%20on%20some%20interval.%0A%0AThis%20technique%20can%20be%20used%20to%20poll%20anything%2C%20%0Anot%20just%20the%20%5Brouter%20service%5D%5Brouter-service%5D%27s%20%5B%60refresh%60%5D%5Brouter-refresh%5D%20method.%0AIt%20could%20be%20used%20for%20any%20function%2C%20%5B%60fetch%60%5D%5Bmdn-fetch%5D%2C%20plain%20old%20functions%2C%20etc.%0A%0AWhen%20polling%2C%20the%20most%20important%20thing%20to%20remember%20is%20that%20the%20polling%20function%20needs%20to%20be%20cancelled%20when%20the%20surrounding%20context%20is%20torn%20down%2C%20or%20if%20the%20app%20is%20destroyed.%20This%20is%20so%20that%20as%20you%20navigate%20within%20your%20app%2C%20or%20while%20running%20tests%2C%20a%20memory%20leak%20does%20not%20occur.%0A%0AThis%20approach%20uses%20%5B%60setInterval%60%5D%5Bmdn-setInterval%5D%20so%20as%20to%20not%20induce%20a%20%5B%60too%20much%20recursion%60%5D%5Bmdn-too-much-recursion%5D%20error.%0A%0A%5Brouter-service%5D%3A%20https%3A%2F%2Fapi.emberjs.com%2Fember%2Frelease%2Fclasses%2FRouterService%0A%5Brouter-refresh%5D%3A%20https%3A%2F%2Fapi.emberjs.com%2Fember%2F4.9%2Fclasses%2FRouterService%2Fmethods%2Frefresh%3Fanchor%3Drefresh%0A%5Broute-model%5D%3A%20https%3A%2F%2Fguides.emberjs.com%2Frelease%2Frouting%2Fspecifying-a-routes-model%2F%0A%5Bmdn-fetch%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFetch_API%0A%5Bmdn-setInterval%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FsetInterval%0A%5Bmdn-too-much-recursion%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FErrors%2FToo_much_recursion%0A%0A%60%60%60gjs%20live%20preview%0Aimport%20Helper%20from%20%27%40ember%2Fcomponent%2Fhelper%27%3B%0Aimport%20%7B%20registerDestructor%20%7D%20from%20%27%40ember%2Fdestroyable%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20cell%20%7D%20from%20%27ember-resources%27%3B%0A%0Aconst%20show%20%3D%20cell(true)%3B%0Aconst%20pollCount%20%3D%20cell(0)%3B%0Aconst%20fetchData%20%3D%20()%20%3D%3E%20%7B%0A%20%20pollCount.current%2B%2B%3B%0A%20%20console.log(%27Fetching%20data%20(for%20pretend%20%2F%20example)%27)%3B%0A%7D%0Aconst%20ONE_SECOND%20%3D%201_000%3B%20%2F%2F%20ms%0A%0A%2F**********************************************************%0A%20*%20DEMO%20starts%20here%2C%20everything%20above%20is%20mostly%20irrelevant%0A%20**********************************************************%2F%0Aclass%20Poll%20extends%20Helper%20%7B%0A%20%20intervalId%20%3D%20null%3B%0A%20%20compute(%5Bfn%5D%2C%20%7B%20interval%20%7D)%20%7B%0A%20%20%20%20if%20(!this.intervalId)%20%7B%0A%20%20%20%20%20%20registerDestructor(this%2C%20()%20%3D%3E%20clearInterval(this.intervalId))%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20clearInterval(this.intervalId)%0A%20%20%20%20this.intervalId%20%3D%20setInterval(fn%2C%20interval)%3B%20%20%20%20%0A%20%20%20%20return%3B%0A%20%20%7D%0A%7D%0A%0A%3Ctemplate%3E%0A%20%20Poll%20count%3A%20%7B%7BpollCount.current%7D%7D%3Cbr%3E%0A%20%20%3Cbutton%20%7B%7Bon%20%27click%27%20show.toggle%7D%7D%3EToggle%3C%2Fbutton%3E%3Cbr%20%2F%3E%0A%20%20%0A%20%20%7B%7B%23if%20show.current%7D%7D%0A%20%20%20%20%20Data%20is%20being%20polled.%0A%0A%20%20%20%20%20%7B%7BPoll%20fetchData%20interval%3DONE_SECOND%7D%7D%0A%20%20%7B%7Belse%7D%7D%0A%20%20%20%20%20Polling%20is%20not%20occurring.%0A%20%20%7B%7B%2Fif%7D%7D%0A%0A%20%20%20%20%0A%3C%2Ftemplate%3E%0A%60%60%60">Using 0 libraries (as of 4.10.0)</a></li>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Polling%20data%0A%0AA%20common%20thing%20folks%20ask%20is%20to%20re-call%20%2F%20re-run%20the%20%5Broute%27s%20model%20hook%5D%5Broute-model%5D%20on%20some%20interval.%0A%0AThis%20technique%20can%20be%20used%20to%20poll%20anything%2C%20%0Anot%20just%20the%20%5Brouter%20service%5D%5Brouter-service%5D%27s%20%5B%60refresh%60%5D%5Brouter-refresh%5D%20method.%0AIt%20could%20be%20used%20for%20any%20function%2C%20%5B%60fetch%60%5D%5Bmdn-fetch%5D%2C%20plain%20old%20functions%2C%20etc.%0A%0AWhen%20polling%2C%20the%20most%20important%20thing%20to%20remember%20is%20that%20the%20polling%20function%20needs%20to%20be%20cancelled%20when%20the%20surrounding%20context%20is%20torn%20down%2C%20or%20if%20the%20app%20is%20destroyed.%20This%20is%20so%20that%20as%20you%20navigate%20within%20your%20app%2C%20or%20while%20running%20tests%2C%20a%20memory%20leak%20does%20not%20occur.%0A%0AThis%20approach%20uses%20%5B%60setInterval%60%5D%5Bmdn-setInterval%5D%20so%20as%20to%20not%20induce%20a%20%5B%60too%20much%20recursion%60%5D%5Bmdn-too-much-recursion%5D%20error.%0A%0A%5Brouter-service%5D%3A%20https%3A%2F%2Fapi.emberjs.com%2Fember%2Frelease%2Fclasses%2FRouterService%0A%5Brouter-refresh%5D%3A%20https%3A%2F%2Fapi.emberjs.com%2Fember%2F4.9%2Fclasses%2FRouterService%2Fmethods%2Frefresh%3Fanchor%3Drefresh%0A%5Broute-model%5D%3A%20https%3A%2F%2Fguides.emberjs.com%2Frelease%2Frouting%2Fspecifying-a-routes-model%2F%0A%5Bmdn-fetch%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFetch_API%0A%5Bmdn-setInterval%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FsetInterval%0A%5Bmdn-too-much-recursion%5D%3A%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FErrors%2FToo_much_recursion%0A%0A%60%60%60gjs%20live%20preview%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20cell%2C%20resource%2C%20resourceFactory%20%7D%20from%20%27ember-resources%27%3B%0A%0Aconst%20show%20%3D%20cell(true)%3B%0Aconst%20pollCount%20%3D%20cell(0)%3B%0Aconst%20fetchData%20%3D%20()%20%3D%3E%20%7B%0A%20%20pollCount.current%2B%2B%3B%0A%20%20console.log(%27Fetching%20data%20(for%20pretend%20%2F%20example)%27)%3B%0A%7D%0Aconst%20ONE_SECOND%20%3D%201_000%3B%20%2F%2F%20ms%0A%0A%2F**********************************************************%0A%20*%20DEMO%20starts%20here%2C%20everything%20above%20is%20mostly%20irrelevant%0A%20**********************************************************%2F%0Aconst%20poll%20%3D%20resourceFactory((fn%2C%20interval)%20%3D%3E%20%7B%0A%20%20return%20resource((%7B%20on%20%7D)%20%3D%3E%20%7B%0A%20%20%20%20let%20x%20%3D%20setInterval(fn%2C%20interval)%3B%20%20%20%20%0A%20%20%20%20on.cleanup(()%20%3D%3E%20clearInterval(x))%3B%0A%20%20%7D)%3B%0A%7D)%3B%0A%0A%3Ctemplate%3E%0A%20%20Poll%20count%3A%20%7B%7BpollCount.current%7D%7D%3Cbr%3E%0A%20%20%3Cbutton%20%7B%7Bon%20%27click%27%20show.toggle%7D%7D%3EToggle%3C%2Fbutton%3E%3Cbr%20%2F%3E%0A%20%20%0A%20%20%7B%7B%23if%20show.current%7D%7D%0A%20%20%20%20%20Data%20is%20being%20polled.%0A%0A%20%20%20%20%20%7B%7Bpoll%20fetchData%20ONE_SECOND%7D%7D%0A%20%20%7B%7Belse%7D%7D%0A%20%20%20%20%20Polling%20is%20not%20occurring.%0A%20%20%7B%7B%2Fif%7D%7D%0A%0A%20%20%20%20%0A%3C%2Ftemplate%3E%0A%60%60%60">Using ember-resources</a></li>
</ul>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Clock%20as%20a%20Resource%0A%0AResources%20can%20maintain%20encapsulated%20state%20and%20provide%20a%20reactive%20single%20value.%0A%0A%60%60%60gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20resource%2C%20cell%20%7D%20from%20%27ember-resources%27%3B%0A%0Aconst%20Clock%20%3D%20resource((%7B%20on%20%7D)%20%3D%3E%20%7B%0A%20%20let%20time%20%3D%20cell(new%20Date())%3B%0A%20%20let%20interval%20%3D%20setInterval(()%20%3D%3E%20time.current%20%3D%20new%20Date()%2C%201000)%3B%0A%0A%20%20on.cleanup(()%20%3D%3E%20clearInterval(interval))%3B%0A%0A%20%20let%20formatter%20%3D%20new%20Intl.DateTimeFormat(%27en-US%27%2C%20%7B%0A%20%20%20%20hour%3A%20%27numeric%27%2C%0A%20%20%20%20minute%3A%20%27numeric%27%2C%0A%20%20%20%20second%3A%20%27numeric%27%2C%0A%20%20%20%20hour12%3A%20true%2C%0A%20%20%7D)%3B%0A%0A%20%20return%20()%20%3D%3E%20formatter.format(time.current)%3B%0A%7D)%3B%0A%0A%3Ctemplate%3E%0A%20%20It%20is%3A%20%3Ctime%3E%7B%7BClock%7D%7D%3C%2Ftime%3E%0A%3C%2Ftemplate%3E%0A%0A%60%60%60%0A">Clock</a> - encapsulated state and providing a single reactive value.</li>
</ul>
<h2 id="rendering">Rendering</h2>
<ul>
<li>elsewhere / with portals via <a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Portalling%20via%20%60in-element%60%0A%0A%60%60%60gjs%20live%0Aconst%20getBySelector%20%3D%20(selector)%20%3D%3E%20document.querySelector(selector)%3B%0A%0A%3Ctemplate%3E%0A%20%20portal%3A%20%3Cdiv%20id%3D%22target%22%3E%3C%2Fdiv%3E%0A%0A%0A%20%20%3Cbr%3E%3Cbr%3E%0A%0A%20%20somewhere%20eles%20in%20your%20app%0A%0A%20%20%7B%7B%23in-element%20(getBySelector%20%27%23target%27)%7D%7D%0A%20%20%20%20hi%2C%20I%20could%20be%20a%20modal%0A%20%20%7B%7B%2Fin-element%7D%7D%0A%0A%20%20is%20content%0A%3C%2Ftemplate%3E%0A%0A%60%60%60"><code>in-element</code></a></li>
<li><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Conditional%20Modifiers%0A%0A%60%60%60gjs%20live%20preview%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0Aimport%20%7B%20modifier%20%7D%20from%20%27ember-modifier%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aexport%20default%20class%20HelloWorld%20extends%20Component%20%7B%0A%20%20%40tracked%20enabled%20%3D%20false%3B%0A%0A%20%20toggle%20%3D%20()%20%3D%3E%20this.enabled%20%3D%20!this.enabled%3B%0A%0A%20%20theModifier%20%3D%20modifier(element%20%3D%3E%20%7B%0A%20%20%20%20element.style.textTransform%20%3D%20%27uppercase%27%3B%0A%0A%20%20%20%20return%20()%20%3D%3E%20element.style.textTransform%20%3D%20%27lowercase%27%3B%0A%20%20%7D)%3B%0A%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%3Cp%20%7B%7B%20(if%20this.enabled%20this.theModifier)%20%7D%7D%3EThe%20modifier%20should%20be%20enabled%3A%20%7B%7Bthis.enabled%7D%7D%3C%2Fp%3E%0A%0A%20%20%20%20%3Cbutton%20%7B%7Bon%20%22click%22%20this.toggle%7D%7D%3Etoggle%20modifier%3C%2Fbutton%3E%0A%20%20%3C%2Ftemplate%3E%0A%7D%0A%60%60%60%0A">conditional modifiers</a></li>
<li><a href="https://limber.glimdown.com/edit?format=gjs&amp;t=import%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20cell%20%7D%20from%20%27ember-resources%27%3B%0A%2F%2F%20shadow-dom%20componentt%20used%20to%20get%20a%20reset%20on%20styling%0A%2F%2F%20this%20is%20showcased%20more%20prominently%20in%20the%20%22Styleguide%22%20demo%0Aimport%20Shadowed%20from%20%27limber%2Fcomponents%2Fshadowed%27%3B%0A%0A%2F%2F%20State%20that%20manages%20the%20%22selected%22%20component%0Aconst%20selected%20%3D%20cell()%3B%0A%0A%2F%2F%20Here%20are%20the%20components%20used%20in%20the%20MAP.%0A%2F%2F%20these%20could%20be%20imported%20from%20other%20files%20(co-located%2C%20template-only%2C%20etc)%0Aconst%20One%20%3D%20%3Ctemplate%3EOne%3C%2Ftemplate%3E%3B%0Aconst%20Two%20%3D%20%3Ctemplate%3ETwo%3C%2Ftemplate%3E%3B%0Aconst%20Three%20%3D%20%3Ctemplate%3EThree%3C%2Ftemplate%3E%3B%0Aconst%20Fallback%20%3D%20%3Ctemplate%3EFallback%20%2F%20nothing%20selecetd%3C%2Ftemplate%3E%3B%0A%0Aconst%20MAP%20%3D%20%7B%0A%20%20%27one%27%3A%20One%2C%0A%20%20%27two%27%3A%20Two%2C%0A%20%20%27three%27%3A%20Three%2C%0A%7D%3B%0A%0A%2F%2F%20This%20is%20a%20helper%2C%20as%20all%20functions%20are%20helpers%0Aconst%20componentFor%20%3D%20(key)%20%3D%3E%20MAP%5Bkey%5D%20%3F%3F%20Fallback%3B%0A%0A%2F%2F%20This%20way%20of%20dealing%20with%20form%2Finput%20event%20binding%20is%20demonstrated%0A%2F%2F%20in%20the%20%22Forms%22%20demo%20in%20the%20%22Select%20demo%22%20menu%20at%20the%20top%20of%20the%20screen%0Aconst%20handleInput%20%3D%20(event)%20%3D%3E%20%7B%0A%20%20let%20formData%20%3D%20new%20FormData(event.currentTarget)%3B%0A%20%20let%20data%20%3D%20Object.fromEntries(formData.entries())%3B%0A%0A%20%20selected.current%20%3D%20data.component%3B%0A%7D%0Aconst%20handleSubmit%20%3D%20(%20event)%20%3D%3E%20%7B%0A%20%20event.preventDefault()%3B%0A%20%20handleInput(event)%3B%0A%7D%3B%0A%0A%3Ctemplate%3E%0A%20%20%3CShadowed%20%40omitStyles%3D%7B%7Btrue%7D%7D%3E%0A%20%20%20%20%3Cform%20%7B%7Bon%20%27input%27%20handleInput%7D%7D%20%7B%7Bon%20%27submit%27%20handleSubmit%7D%7D%3E%0A%20%20%20%20%20%20%3Cfieldset%3E%0A%20%20%20%20%20%20%20%20%3Clegend%3EChoose%20a%20component%3C%2Flegend%3E%0A%20%20%20%20%20%20%20%20%3Clabel%3EOne%20%20%20%3Cinput%20name%3D%22component%22%20type%3D%22radio%22%20value%3D%22one%22%3E%3C%2Flabel%3E%0A%20%20%20%20%20%20%20%20%3Clabel%3ETwo%20%20%20%3Cinput%20name%3D%22component%22%20type%3D%22radio%22%20value%3D%22two%22%3E%3C%2Flabel%3E%0A%20%20%20%20%20%20%20%20%3Clabel%3EThree%20%3Cinput%20name%3D%22component%22%20type%3D%22radio%22%20value%3D%22three%22%3E%3C%2Flabel%3E%0A%20%20%20%20%20%20%3C%2Ffieldset%3E%0A%20%20%20%20%3C%2Fform%3E%0A%0A%20%20%20%20%3Cbr%3E%0A%20%20%20%20%7B%7B%23let%20(componentFor%20selected.current)%20as%20%7CSelected%7C%7D%7D%0A%20%20%20%20%20%20%3CSelected%20%2F%3E%0A%20%20%20%20%7B%7B%2Flet%7D%7D%0A%20%20%3C%2FShadowed%3E%0A%3C%2Ftemplate%3E">dynamically rendering components, statically</a></li>
</ul>
<h2 id="effects">Effects</h2>
<p>Note that effects <em>should</em> be avoided (in any ecosystem, not just ember). They are an easy thing to grab for, but often a symptom of a bigger problem. Most effects I've seen (whether implemented as helpers (as in the demos below) or as modifiers (like <code>@ember/render-modifiers</code>) should actually just be a <code>@cached</code> getter.  Derived data patterns are much easier to debug and provide much nicer stack traces, whereas effects are "pretty much" observers, which can suffer from "spooky action at a distance" or "weird stacktrace".</p>
<ul>
<li>calling a function once</li>
<li>calling a function in response to arg changes</li>
<li>an effect that relies on auto-tracking to re-run</li>
</ul>
<p><a href="https://limber.glimdown.com/edit?format=glimdown&amp;t=%23%20Effects%20in%20Ember%0A%0AI%20don%27t%20have%20any%20valid%20use%20cases%20for%20effects%20that%20can%20fit%20in%20tiny%20demos%2C%20so%20I%27m%20just%20logging%20to%20the%20console%20when%20each%20effect%20function%20is%20called.%0A%0AWhen%20observing%20the%20behavior%20of%20these%20demos%2C%20be%20sure%20to%20have%20the%20browser%20console%20open.%0A%0A%23%23%20Layout%20Effect%0A%0ARun%20some%20code%20during%20initial%20render%0A%60%60%60gjs%20live%20preview%0Aconst%20myEffect%20%3D%20()%20%3D%3E%20console.log(%27runs%20once%27)%3B%0A%0A%3Ctemplate%3E%0A%20%20%7B%7B%20(myEffect)%20%7D%7D%0A%3C%2Ftemplate%3E%0A%60%60%60%0A%0A%23%23%20Effect%20when%20arguments%20change%0A%0AIn%20ember%2C%20you%20usually%20don%27t%20need%20to%20do%20this%0Abecause%20derived%20data%20patterns%20have%20usually%20got%20covered%0A(getters%2C%20resources%2C%20etc).%0A%0A%60%60%60gjs%20live%20preview%0Aimport%20state%20from%20%27limber%2Fhelpers%2Fstate%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0A%0Aconst%20myEffect%20%3D%20(foo)%20%3D%3E%20console.log(%27foo%20is%20now%20%27%2C%20foo)%3B%0A%0Aconst%20WithEffect%20%3D%20%3Ctemplate%3E%0A%20%20%7B%7B%20(myEffect%20%40foo)%7D%7D%0A%3C%2Ftemplate%3E%3B%0A%0A%3Ctemplate%3E%0A%20%20%7B%7B%23let%20(state)%20as%20%7Cs%7C%7D%7D%0A%20%20%20%20%3Cinput%20%7B%7Bon%20%27input%27%20s.increment%7D%7D%20class%3D%22border%22%20%2F%3E%0A%20%20%20%20%3CWithEffect%20%40foo%3D%7B%7Bs.value%7D%7D%20%2F%3E%0A%20%20%7B%7B%2Flet%7D%7D%0A%3C%2Ftemplate%3E%0A%60%60%60%0A%0A%23%23%20Effects%20auto-track%0A%0AIf%20accessing%20tracked%20data%20within%20a%20function%20or%20effect%2C%20%0Ait%20will%20auto-track%2C%20and%20re-run%20when%20the%20tracked%20data%20changes.%0A%0A%60%60%60gjs%20live%0Aimport%20Component%20from%20%27%40glimmer%2Fcomponent%27%3B%0Aimport%20%7B%20on%20%7D%20from%20%27%40ember%2Fmodifier%27%3B%0Aimport%20%7B%20tracked%20%7D%20from%20%27%40glimmer%2Ftracking%27%3B%0A%0Aexport%20default%20class%20Demo%20extends%20Component%20%7B%0A%20%20%40tracked%20value%3B%0A%20%20%0A%20%20myEffect%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20console.log(%60tracked%20value%3A%20%24%7Bthis.value%7D%60)%3B%0A%20%20%7D%0A%0A%20%20update%20%3D%20(event)%20%3D%3E%20this.value%20%3D%20event.target.value%3B%0A%20%20%0A%20%20%3Ctemplate%3E%0A%20%20%20%20%7B%7B%20(this.myEffect)%20%7D%7D%0A%0A%20%20%20%20%3Cinput%20%7B%7Bon%20%27input%27%20this.update%7D%7D%20class%3D%22border%22%20%2F%3E%0A%20%20%3C%2Ftemplate%3E%0A%7D%0A%60%60%60">demos</a></p>
<p><em>Originally posted <a href="https://discuss.emberjs.com/t/collection-of-strict-mode-template-demos-of-various-concepts/19637">here</a> on the <a href="https://discuss.emberjs.com/">Ember Forums</a> -- there is some additional discussion there as well.</em></p>]]></description><link>https://nullvoxpopuli.com/2022-09-05-gjs-cookbook-examples</link><guid isPermaLink="true">https://nullvoxpopuli.com/2022-09-05-gjs-cookbook-examples</guid><pubDate>Tue, 06 Sep 2022 00:30:07 GMT</pubDate></item><item><title><![CDATA[<template> quickstart]]></title><description><![CDATA[<p>This is a quick reference guide for getting started with gjs/gts aka <code>&lt;template&gt;</code>.</p>
<p>The RFC that concluded the research in to this new file format is<br />
<a href="https://github.com/emberjs/rfcs/pull/779">RFC#779 First-Class Component Templates</a></p>
<h2 id="for-all-projects">For all projects</h2>
<ol>
<li><p>For syntax highlighting:</p>
<ul>
<li>in neovim and VSCode, see <a href="https://github.com/ember-template-imports/ember-template-imports/#editor-integrations">here</a></li>
<li>on the web,<ul>
<li><code>highlight.js</code> via <a href="https://github.com/NullVoxPopuli/highlightjs-glimmer/">highlightjs-glimmer</a></li>
<li><a href="https://github.com/shikijs/shiki/tree/main"><code>shiki</code> </a> has gjs support built in</li></ul></li></ul></li>
<li><p>For type checking <code>gts</code>, you'll use <a href="https://github.com/typed-ember/glint"><code>glint</code></a> instead of <code>tsc</code>.<br />
Ember Glint documentation is here <a href="https://typed-ember.gitbook.io/glint/environments/ember">on the Glint docs site</a>.<br />
<br><br />
Ensure that your tsconfig.json has </p>
<pre><code class="json language-json">{
  "compilerOptions": { /* ... */ },
  "glint": {
    "environment": [
      "ember-loose",
      "ember-template-imports"
    ]
  }
}</code></pre>
<p><br><br />
Note that in Glint projects, you'll want to disable <code>tsserver</code>, so you don't have double-reported errors.</p></li>
<li><p>For linting, there are two paths:</p>
<ul>
<li>Quickest:<br><br />
use <code>configs.ember()</code> from <a href="https://github.com/NullVoxPopuli/eslint-configs"><code>@nullvoxpopuli/eslint-configs</code></a> </li>
<li>Modifying you're existing lint command:<br></li></ul>
<pre><code class="bash language-bash">  eslint . --ext js,ts,gjs,gts</code></pre>
<p>if you otherwise don't specify extensions, having a sufficiently  new enough <a href="https://github.com/ember-cli/ember-new-output/blob/v5.1.0/.eslintrc.js#L19">lint config from the app blueprint</a> should "just work"</p>
<ul>
<li>make sure you have at least <code>eslint-plugin-ember@12.0.2</code></li>
<li>use the ember eslint parser in your eslint config, (mentioned in the <a href="https://github.com/ember-cli/eslint-plugin-ember?tab=readme-ov-file#gtsgjs">eslint-plugin-ember README</a>)</li></ul>
<pre><code class="js language-js">   module.exports = {
      // ...
      overrides: [
        // ...
        {
          files: ['**/*.gts'],
          plugins: ['ember'],
          parser: 'ember-eslint-parser',
        },
        {
          files: ['**/*.gjs'],
          plugins: ['ember'],
          parser: 'ember-eslint-parser',
        },
      ],
   };</code></pre>
<p>To get linting working in VSCode, you'll need to modify your settings (and be sure to include the defaults as well for both of these settings):</p>
<pre><code class="json language-json">"eslint.probe": [
 // ...
 "glimmer-js", 
 "glimmer-ts"
],
"eslint.validate": [
  // ...
  "glimmer-js",
  "glimmer-ts"
],
// ...
"prettier.documentSelectors": ["**/*.gjs", "**/*.gts"],</code></pre>
<p>For neovim,</p>
<pre><code class="lua language-lua">local lsp = require('lspconfig')

-- ✂️ 

local eslint = lsp['eslint']

eslint.setup({
  filetypes = { 
    "javascript", "typescript", 
    "typescript.glimmer", "javascript.glimmer", 
    "json", 
    "markdown" 
  },
  on_attach = function(client, bufnr)
    vim.api.nvim_create_autocmd("BufWritePre", {
      buffer = bufnr,
      command = "EslintFixAll",
    })
  end,
})</code></pre>
<p>neovim has support for <code>typescript.glimmer</code> and <code>javascript.glimmer</code> built in. <a href="https://github.com/sheerun/vim-polyglot"><code>vim-polyglot</code></a> breaks neovim's built-in syntax resolving, so uninstall that if you have it (neovim ships with treesitter which has very extensive language support).</p></li>
<li><p>For prettier/formatting, you'll need <a href="https://github.com/gitKrystan/prettier-plugin-ember-template-tag/"><code>prettier-plugin-ember-template-tag</code></a><br />
And for the best experience / workflow, run eslint and prettier separately (without the popular eslint-plugin-prettier)</p></li>
</ol>
<h2 id="in-an-app">In an App</h2>
<ol>
<li><p>Install <a href="https://github.com/ember-template-imports/ember-template-imports/"><code>ember-template-imports</code></a>.</p></li>
<li><p>If you use TypeScript,</p>
<ol>
<li>update your <a href="https://github.com/emberjs/ember-cli-babel?tab=readme-ov-file#options">babel config</a> to have:</li></ol>
<pre><code class="js language-js">    "plugins": [
      [
        "@babel/plugin-transform-typescript",
        { "allExtensions": true, "onlyRemoveTypeImports": true, "allowDeclareFields": true }
      ],
      // ...</code></pre>
<ol start="3">
<li>update your tsconfig.json to have (in <code>compilerOptions</code>)</li></ol>
<pre><code class="js language-js">  "verbatimModuleSyntax": true,</code></pre></li>
</ol>
<h2 id="in-a-v1-addon">In a v1 Addon</h2>
<ol>
<li><p>Install <a href="https://github.com/ember-template-imports/ember-template-imports/"><code>ember-template-imports</code></a>.</p>
<pre><code class="bash language-bash">npm add ember-template-imports</code></pre>
<p>it is important that this library be in <code>dependencies</code>, not <code>devDependencies</code></p></li>
<li><p>If you use TypeScript,</p>
<ol>
<li>update your <a href="https://github.com/emberjs/ember-cli-babel?tab=readme-ov-file#options">babel config</a> to have:</li></ol>
<pre><code class="js language-js">    "plugins": [
      [
        "@babel/plugin-transform-typescript",
        { "allExtensions": true, "onlyRemoveTypeImports": true, "allowDeclareFields": true }
      ],
      // ...</code></pre>
<ol start="2">
<li>update your tsconfig.json to have (in <code>compilerOptions</code>)</li></ol>
<pre><code class="js language-js">  "verbatimModuleSyntax": true,
  "allowImportingTsExtensions": true,</code></pre></li>
</ol>
<h2 id="in-a-v2-addon">In a v2 Addon</h2>
<ol>
<li><code>@embroider/addon-dev</code> provides a <code>addon.gjs()</code> plugin.</li>
</ol>
<h2 id="usage-and-patterns">Usage and Patterns</h2>
<p>See the interactive glimmer tutorial: <a href="https://tutorial.glimdown.com">https://tutorial.glimdown.com</a></p>
<h2 id="notes">Notes</h2>
<p><code>ember-template-imports</code> easily provides a prototype of the features described by <code>RFC#779</code>, but <code>embber-template-imports</code> is not the final form of the <code>&lt;template&gt;</code> feature -- it as an easy way to set up the support today, but future techniques for enabling <code>&lt;template&gt;</code> will be easier. When reviewing the README for <code>ember-template-imports</code>, ignore everything about <code>hbs</code>, it will not be supported.   </p>
<h2 id="example-projects-using-gjsgts">Example Projects using gjs/gts</h2>
<p>Each of these projects is setup with linting, typechecking, etc</p>
<p>Hopefully they can be a reference for when issues are encountered upon setup</p>
<ul>
<li><p><a href="https://github.com/NullVoxPopuli/limber">The Limber Project (REPL and Tutorial)</a></p></li>
<li><p><a href="https://github.com/NullVoxPopuli/ember-resources/">ember-resources</a></p></li>
<li><p><a href="https://github.com/universal-ember/ember-primitives/">ember-primitives</a></p></li>
<li><p><a href="https://github.com/CrowdStrike/ember-headless-form/">ember-headless-form</a></p></li>
<li><p><a href="https://github.com/CrowdStrike/ember-headless-table/">ember-headless-table</a></p>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="https://mfeckie.dev/glint-and-ember-template-imports/">Glint and &lt;template&gt;</a></li></ul></li>
</ul>]]></description><link>https://nullvoxpopuli.com/2023-01-04-setting-up-gjs</link><guid isPermaLink="true">https://nullvoxpopuli.com/2023-01-04-setting-up-gjs</guid><pubDate>Sun, 04 Jun 2023 12:39:26 GMT</pubDate></item><item><title><![CDATA[null_ls being difficult was all in my head]]></title><description><![CDATA[<p>For a long time, I'd been hearing about <a href="https://github.com/jose-elias-alvarez/null-ls.nvim">null_ls</a> on the <a href="https://www.reddit.com/r/neovim/">neovim subreddit</a> and how it solves all my non-language-server tooling problems. </p>
<p>I had given a brief look at <code>null_ls</code> a while back, and didn't really comprehend what it wanted of me. I didn't understand how <em>my</em> neovim config applied, and how <em>I</em> setup my language servers would be applicable or not.</p>
<p>It wasn't until <em>yesterday</em> when I was playing around with custom <code>root_dir</code> functions on <code>tsserver</code> and <code>glint</code> language server. </p>
<p>Normal language servers are set up by requiring <code>lspconfig</code> and calling the appropriate server's setup method:</p>
<pre><code class="lua language-lua">local lsp = require('lspconfig')

lsp[serverName].setup({   
  -- config here
})</code></pre>
<p>and <code>null_ls</code> is similar:</p>
<pre><code class="lua language-lua">local null_ls = require('null-ls')

null_ls.setup({
 -- config here
})</code></pre>
<p>and that's the whole gist 🎉.</p>
<p>Of course, just being able to call <code>setup</code> isn't enough, I wanted the following features:</p>
<ul>
<li>eslint <ul>
<li>diagnostics and code-actions </li>
<li>run on save</li></ul></li>
<li>prettier <ul>
<li>diagnostics and code-actions </li>
<li>run on save</li></ul></li>
<li>spell check diagnostics and actions <ul>
<li>a single machine-global file to store my custom words</li>
<li>I had lost this when I switched away from <a href="https://github.com/neoclide/coc.nvim">CoC</a></li></ul></li>
</ul>
<p>It turns out that all of these integrations are built-in to <code>null_ls</code>!!</p>
<p>As I was browsing for which built-in configs would work best for me, I learned that both prettier and eslint have <em>daemon</em> versions, <a href="https://www.npmjs.com/package/eslint_d"><code>eslint_d</code></a> and <a href="https://github.com/fsouza/prettierd"><code>prettierd</code></a>. This is important to me because I want my editor to be as fast and as responsive as possible, and in order to accomplish that with <code>node</code>'s boot time, daemonizing just makes sense to help out with time and memory and <em>block</em> the main thread for the least amount of time possible while performing "format on save". Both README's for these two tools explain the philosophy behind their approaches.</p>
<p>To make <code>null_ls</code> as obvious and as isolated as possible, I did all configuration in a separate file that I require from my main lsp-config lua file.</p>
<p>To set up all <code>null_ls</code> stuff, I do this "somewhere":</p>
<pre><code class="lua language-lua">require('plugin-config.lsp.integrations')</code></pre>
<p>where I have a <code>plugin-config/lsp/integrations.lua</code> file where all the <code>null_ls</code> code lives.</p>
<h2 id="the-code--config">The Code / config</h2>
<p>In my file where I configure what plugins I want, I have this:</p>
<pre><code class="lua language-lua">use {
  'jose-elias-alvarez/null-ls.nvim',
  requires = { "nvim-lua/plenary.nvim" }
}</code></pre>
<p>I use <a href="https://github.com/wbthomason/packer.nvim">packer</a> for package management.</p>
<p>The <a href="https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/CONFIG.md">official docs</a> are really good, and I really just needed to have read them.</p>
<p>Then my <code>plugin-config/lsp/integration.lua</code> file has these contents, below -- this is the combination of a bunch of docs, example code, and other people's solution to problems. I'll explain in comments in the snippet in case things may not be clear.</p>
<pre><code class="lua language-lua">---------------------------------------------------------
--
-- NULL LS is for hooking up non-LSP tools to the LSP UX
--
--
-- Be sure to :checkhealth to see if any underlying tools are missing
--
-- pnpm add --global @fsouza/prettierd cspell typescript
--
---------------------------------------------------------
local null_ls = require('null-ls')
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})

-- TODO: figure out how to wire up ember-template-lint
local lsp_formatting = function(buffer)
  vim.lsp.buf.format({
    filter = function(client)
      -- By default, ignore any formatters provider by other LSPs 
      -- (such as those managed via lspconfig or mason)
      -- Also "eslint as a formatter" doesn't work :(
      return client.name == "null-ls"
    end,
    bufnr = buffer,
  })
end

-- Format on save
-- https://github.com/jose-elias-alvarez/null-ls.nvim/wiki/Avoiding-LSP-formatting-conflicts#neovim-08
local on_attach = function(client, buffer)
  -- the Buffer will be null in buffers like nvim-tree or new unsaved files
  if (not buffer) then
    return
  end

  if client.supports_method("textDocument/formatting") then
    vim.api.nvim_clear_autocmds({ group = augroup, buffer = buffer })
    vim.api.nvim_create_autocmd("BufWritePre", {
      group = augroup,
      buffer = buffer,
      callback = function()
        lsp_formatting(buffer)
      end,
    })
  end
end

null_ls.setup({
  sources = {
    -- Prettier, but faster (daemonized)
    null_ls.builtins.formatting.prettierd.with({
      filetypes = { 
        "css", "json", "jsonc","javascript", "typescript",
        "javascript.glimmer", "typescript.glimmer",
        "handlebars"
      }
    }),

    -- Code actions for staging hunks, blame, etc 
    null_ls.builtins.code_actions.gitsigns,
    null_ls.builtins.completion.luasnip,

    -- Spell check that has better tooling
    -- all stored locally
    -- https://github.com/streetsidesoftware/cspell
    null_ls.builtins.diagnostics.cspell.with({
      -- This file is symlinked from my dotfiles repo
      extra_args = { "--config", "~/.cspell.json" }
    }),
    null_ls.builtins.code_actions.cspell.with({
      -- This file is symlinked from my dotfiles repo
      extra_args = { "--config", "~/.cspell.json" }
    })
    -- null_ls.builtins.completion.spell,
  },
  on_attach = on_attach
})</code></pre>
<h2 id="eslint">ESLint</h2>
<p>The native ESLint LSP is similar to prettierd, in that it has a long-running process from within which to invoke eslint from.<br />
I did this in a <a href="https://github.com/NullVoxPopuli/dotfiles/commit/a438c38c23630928a2b5f4f985a4d082b4b7a3be">separate commit</a>.<br />
You can see that I tried out <code>eslint_d</code> (and I've since removed it from the above code snippets in this post), <br />
but I ran in to an issue where the way <code>eslint_d</code> manages processes caused a lot of zombie processes when I'd eventually exit neovim.<br />
Since node tends to be memory heavy, having so many zombie processes just would not be acceptable.</p>
<p>The thing missing from the ESLint LSP is formatting with the LSP formatting integration.</p>
<p>The main things to have when using ESLint with "--fix on save", are:</p>
<pre><code class="lua language-lua">local eslint = require('lspconfig').eslint

eslint.setup({
  on_attach = function(client, bufnr)
    vim.api.nvim_create_autocmd("BufWritePre", {
      buffer = bufnr,
      command = "EslintFixAll",
    })
  end,
})</code></pre>
<p>And then I could <em>remove</em> the entries for <code>diagnostics.eslint_d</code> and <code>formatting.eslint_d</code> in my <code>null_ls.setup</code>.</p>]]></description><link>https://nullvoxpopuli.com/2023-03-13-null-ls</link><guid isPermaLink="true">https://nullvoxpopuli.com/2023-03-13-null-ls</guid><pubDate>Mon, 13 Mar 2023 15:40:00 GMT</pubDate></item><item><title><![CDATA[Passwordless Authentication on Ubuntu with a Yubikey]]></title><description><![CDATA[<p>I've been using my desktop computer a lot more lately, and one thing I miss from my <a href="https://frame.work/">frame.work</a> laptop is the passwordless authentication / passwordless sudo.<br />
On the frame.work, it uses a fingerprint reader, which makes <code>sudo</code> a breeze. Since I've been using my desktop with its big screen, big GPU (not that I do GPU work), and faster-than-Apple-M1-maybe-same-as-M2 processor (AMD Ryzen 9), I want the same passwordless experience!</p>
<p>Here is how to set up passwordless authentication with a <a href="https://www.yubico.com/products/yubikey-5-overview/">Yubikey</a>:</p>
<pre><code class="bash language-bash">sudo apt install libpam-u2f
mkdir ~/.config/Yubico # do not commit this directory to a dotfiles repo or anything like that
pamu2fcfg &gt; ~/.config/Yubico/u2f_keys # once the light blinks on your yubikey, press the button</code></pre>
<p>Lastly, configure the type of auth that the Yubikey will be used for by editing <code>/etc/pam.d/sudo</code>:</p>
<pre><code class="diff language-diff">  # Set up user limits from /etc/security/limits.conf.
  session    required   pam_limits.so

  session    required   pam_env.so readenv=1 user_readenv=0
  session    required   pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
+
+ auth sufficient pam_u2f.so

  @include common-auth
  @include common-account
  @include common-session-noninteractive</code></pre>
<p>Docs for the <a href="https://packages.ubuntu.com/jammy/pamu2fcfg"><code>pamu2fcfg</code></a> util can be <a href="https://developers.yubico.com/pam-u2f/Manuals/pamu2fcfg.1.html">found here</a>.</p>
<p>For reference, my versions at the time of writing:</p>
<pre><code>❯ lsb_release -rd
Description:    Ubuntu 22.04.2 LTS
Release:    22.04

❯ uname -r
5.15.0-43-generic</code></pre>
<p><strong>What happens if you don't have your Yubikey nearby?</strong><br />
You'll be asked for your <code>sudo</code> password, as you would have been previously.</p>
<p><strong>What happens if the Yubikey is not plugged in?</strong><br />
You'll be asked for your <code>sudo</code> password, as you would have been previously. <br />
Passwordless auth is only enabled when the Yubikey is plugged in.</p>]]></description><link>https://nullvoxpopuli.com/2023-03-13-yubikey-sudo</link><guid isPermaLink="true">https://nullvoxpopuli.com/2023-03-13-yubikey-sudo</guid><pubDate>Mon, 13 Mar 2023 15:40:00 GMT</pubDate></item><item><title><![CDATA[hifi sourcemaps]]></title><description><![CDATA[<p>If you're using <a href="https://webpack.js.org/">webpack</a> with Ember, you may want to configure the <a href="https://webpack.js.org/configuration/devtool/"><code>devtool</code></a> option to have higher fidelity sourcemaps. Note though that on bigger projects (and I mean more than a few dozens of MB of JS), enabling the <code>source-map</code> option can increase build time by up to 40% (from at least what I've seen).</p>
<p>If you're using:<br />
<em>ember-auto-import</em>:</p>
<pre><code class="js language-js">// ember-cli-build.js
let app = new EmberApp(defaults, {
  autoImport: {
    webpack: {
      devtool: 'source-map',
    }
  }
  /* ... */
});</code></pre>
<p><em>embroider</em>:</p>
<pre><code class="js language-js">// ember-cli-build.js
return require('@embroider/compat').compatBuild(app, Webpack, {
  /* ... */
  packagerOptions: {
    webpackConfig: {
      devtool: 'source-map',
      /* ... */
    },
  },
});</code></pre>
<h2 id="a-simple-component">A simple component</h2>
<h3 id="with-the-defaults">with the defaults</h3>
<p><img src="/images/sourcemaps/to-eval.png" alt="default devtool option" /></p>
<p><details><summary>The code from the screenshot</summary></p>
<pre><code class="ts language-ts">__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   Header: () =&gt; (/* binding */ Header)
/* harmony export */ });
/* harmony import */ var _header_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./header.css */ "./components/header.css");
/* harmony import */ var ember_primitives__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ember-primitives */ "../../.pnpm/ember-primitives@0.10.1_@babel+core@7.23.3_@ember+test-helpers@3.2.1_@glimmer+component@1.1.2_2udfmchmj4htznuxtzf4ynvf4y/node_modules/ember-primitives/dist/components/external-link.js");
/* harmony import */ var _icons_fa_external_link__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./icons/fa/external-link */ "./components/icons/fa/external-link.ts");
/* harmony import */ var _universal_ember_preem__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @universal-ember/preem */ "../../.pnpm/@universal-ember+preem@0.1.7_@babel+core@7.23.3_@ember+test-helpers@3.2.1_@glimmer+component@_orczibosv232nhi52ufgg3bjky/node_modules/@universal-ember/preem/dist/index.js");
/* harmony import */ var _ember_template_factory__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ember/template-factory */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/template-factory/index.js");
/* harmony import */ var _ember_component__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ember/component */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/component/index.js");
/* harmony import */ var _ember_component_template_only__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ember/component/template-only */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/component/template-only.js");







const Header = (0,_ember_component__WEBPACK_IMPORTED_MODULE_4__.setComponentTemplate)((0,_ember_template_factory__WEBPACK_IMPORTED_MODULE_3__.createTemplateFactory)(
/*

  &lt;header&gt;
    &lt;div&gt;
      &lt;ExternalLink class="github" href="https://github.com/NullVoxPopuli/package-majors"&gt;
        &lt;img alt="" src="/images/github-logo.png" /&gt;
        GitHub
        &lt;Arrow /&gt;
      &lt;/ExternalLink&gt;
    &lt;/div&gt;

    &lt;div&gt;
      &lt;ThemeToggle /&gt;
    &lt;/div&gt;
  &lt;/header&gt;

*/
{
  "id": "YMACeO+O",
  "block": "[[[1,\"\\n  \"],[10,\"header\"],[12],[1,\"\\n    \"],[10,0],[12],[1,\"\\n      \"],[8,[32,0],[[24,0,\"github\"],[24,6,\"https://github.com/NullVoxPopuli/package-majors\"]],null,[[\"default\"],[[[[1,\"\\n        \"],[10,\"img\"],[14,\"alt\",\"\"],[14,\"src\",\"/images/github-logo.png\"],[12],[13],[1,\"\\n        GitHub\\n        \"],[8,[32,1],null,null,null],[1,\"\\n      \"]],[]]]]],[1,\"\\n    \"],[13],[1,\"\\n\\n    \"],[10,0],[12],[1,\"\\n      \"],[8,[32,2],null,null,null],[1,\"\\n    \"],[13],[1,\"\\n  \"],[13],[1,\"\\n\"]],[],false,[]]",
  "moduleName": "/home/nvp/Development/NullVoxPopuli/package-majors/node_modules/.embroider/rewritten-app/components/header.ts",
  "scope": () =&gt; [ember_primitives__WEBPACK_IMPORTED_MODULE_6__.ExternalLink, _icons_fa_external_link__WEBPACK_IMPORTED_MODULE_1__.Arrow, _universal_ember_preem__WEBPACK_IMPORTED_MODULE_2__.ThemeToggle],
  "isStrictMode": true
}), (0,_ember_component_template_only__WEBPACK_IMPORTED_MODULE_5__["default"])());

//# sourceURL=webpack://package-majors/./components/header.ts?</code></pre>
<p></details></p>
<h3 id="with-devtool-source-map">with <code>devtool: 'source-map'</code></h3>
<p><img src="/images/sourcemaps/to-source-map.png" alt="setting devtool to 'source-map' shows nearly original source" /></p>
<p><details><summary>The code from the screenshot</summary></p>
<pre><code class="ts language-ts">import { template } from "@ember/template-compiler";
import './header.css';
import { ThemeToggle } from '@universal-ember/preem';
import { ExternalLink } from 'ember-primitives';
import { Arrow } from './icons/fa/external-link';
export const Header = template(`
  &lt;header&gt;
    &lt;div&gt;
      &lt;ExternalLink class="github" href="https://github.com/NullVoxPopuli/package-majors"&gt;
        &lt;img alt="" src="/images/github-logo.png" /&gt;
        GitHub
        &lt;Arrow /&gt;
      &lt;/ExternalLink&gt;
    &lt;/div&gt;

    &lt;div&gt;
      &lt;ThemeToggle /&gt;
    &lt;/div&gt;
  &lt;/header&gt;
`, {
    eval () {
        return eval(arguments[0]);
    }
});</code></pre>
<p></details></p>
<h2 id="debugging">Debugging</h2>
<p>Using <code>devtool: 'source-map'</code> also enables in-browser breakpoints that more closely match the code in the source. With gjs/gts it's not <em>original</em> source, as browsers don't know what gjs/gts is, but it's "pretty close".</p>
<h3 id="with-the-defaults-1">with the defaults</h3>
<p>With the default, a bunch of paths end up emitted in the source.</p>
<p><img src="/images/sourcemaps/class-eval.png" alt="example with devtool set to the default paused on line 66, showing a bunch of paths exposed in the code" /></p>
<p><details><summary>The code from the screenshot</summary></p>
<pre><code class="ts language-ts">__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   Search: () =&gt; (/* binding */ Search)
/* harmony export */ });
/* harmony import */ var _glimmer_component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @glimmer/component */ "../rewritten-packages/@glimmer/component.dac0f4a2/node_modules/@glimmer/component/index.js");
/* harmony import */ var _ember_service__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ember/service */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/service/index.js");
/* harmony import */ var ember_primitives__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ember-primitives */ "../../.pnpm/ember-primitives@0.10.1_@babel+core@7.23.3_@ember+test-helpers@3.2.1_@glimmer+component@1.1.2_2udfmchmj4htznuxtzf4ynvf4y/node_modules/ember-primitives/dist/components/form.js");
/* harmony import */ var _ember_helper__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ember/helper */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/helper/index.js");
/* harmony import */ var _name_input__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./name-input */ "./components/name-input.ts");
/* harmony import */ var _show_minors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./show-minors */ "./components/show-minors.ts");
/* harmony import */ var _show_old__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./show-old */ "./components/show-old.ts");
/* harmony import */ var _ember_template_factory__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ember/template-factory */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/template-factory/index.js");
/* harmony import */ var _ember_component__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ember/component */ "../rewritten-packages/ember-source.848c2a54/node_modules/ember-source/@ember/component/index.js");
/* harmony import */ var _home_nvp_Development_NullVoxPopuli_package_majors_node_modules_pnpm_decorator_transforms_1_0_1_babel_core_7_23_3_node_modules_decorator_transforms_dist_runtime_cjs__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../.pnpm/decorator-transforms@1.0.1_@babel+core@7.23.3/node_modules/decorator-transforms/dist/runtime.cjs */ "../../.pnpm/decorator-transforms@1.0.1_@babel+core@7.23.3/node_modules/decorator-transforms/dist/runtime.cjs");
/* harmony import */ var _home_nvp_Development_NullVoxPopuli_package_majors_node_modules_pnpm_decorator_transforms_1_0_1_babel_core_7_23_3_node_modules_decorator_transforms_dist_runtime_cjs__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_home_nvp_Development_NullVoxPopuli_package_majors_node_modules_pnpm_decorator_transforms_1_0_1_babel_core_7_23_3_node_modules_decorator_transforms_dist_runtime_cjs__WEBPACK_IMPORTED_MODULE_8__);










/**
 * This is triggered on every value change,
 * which we don't need for this app.
 * The early return makes it a normal submit,
 * so the &lt;Form&gt; abstraction is basically just doing the
 * `FormData` conversion for us.
 */
function handleSubmit(onChange1, data1, eventType1) {
  if (eventType1 !== 'submit') return;
  onChange1(data1);
}
class Search extends _glimmer_component__WEBPACK_IMPORTED_MODULE_0__["default"] {
  static {
    (0,_ember_component__WEBPACK_IMPORTED_MODULE_7__.setComponentTemplate)((0,_ember_template_factory__WEBPACK_IMPORTED_MODULE_6__.createTemplateFactory)(
    /*

        &lt;Form @onChange={{fn handleSubmit this.updateSearch}}&gt;
          &lt;NameInput @value={{this.last.packages}} /&gt;

          &lt;ShowMinors checked={{this.last.minors}} /&gt;
          &lt;ShowOld checked={{this.last.old}} /&gt;
        &lt;/Form&gt;

    */
    {
      "id": "q4ddJ3hO",
      "block": "[[[1,\"\\n    \"],[8,[32,0],null,[[\"@onChange\"],[[28,[32,1],[[32,2],[30,0,[\"updateSearch\"]]],null]]],[[\"default\"],[[[[1,\"\\n      \"],[8,[32,3],null,[[\"@value\"],[[30,0,[\"last\",\"packages\"]]]],null],[1,\"\\n\\n      \"],[8,[32,4],[[16,\"checked\",[30,0,[\"last\",\"minors\"]]]],null,null],[1,\"\\n      \"],[8,[32,5],[[16,\"checked\",[30,0,[\"last\",\"old\"]]]],null,null],[1,\"\\n    \"]],[]]]]],[1,\"\\n  \"]],[],false,[]]",
      "moduleName": "/home/nvp/Development/NullVoxPopuli/package-majors/node_modules/.embroider/rewritten-app/components/search.ts",
      "scope": () =&gt; [ember_primitives__WEBPACK_IMPORTED_MODULE_9__.Form, _ember_helper__WEBPACK_IMPORTED_MODULE_2__.fn, handleSubmit, _name_input__WEBPACK_IMPORTED_MODULE_3__.NameInput, _show_minors__WEBPACK_IMPORTED_MODULE_4__.ShowMinors, _show_old__WEBPACK_IMPORTED_MODULE_5__.ShowOld],
      "isStrictMode": true
    }), this);
  }
  static {
    (0,_home_nvp_Development_NullVoxPopuli_package_majors_node_modules_pnpm_decorator_transforms_1_0_1_babel_core_7_23_3_node_modules_decorator_transforms_dist_runtime_cjs__WEBPACK_IMPORTED_MODULE_8__.f)(this, "router", [_ember_service__WEBPACK_IMPORTED_MODULE_1__.service]);
  }
  #router = ((0,_home_nvp_Development_NullVoxPopuli_package_majors_node_modules_pnpm_decorator_transforms_1_0_1_babel_core_7_23_3_node_modules_decorator_transforms_dist_runtime_cjs__WEBPACK_IMPORTED_MODULE_8__.i)(this, "router"), void 0);
  get last() {
    let qps1 = this.router.currentRoute?.queryParams;
    let minors1 = qps1?.['minors'];
    let packages1 = qps1?.['packages'];
    let old1 = qps1?.['old'];
    return {
      packages: packages1 ? `${packages1}` : '',
      minors: minors1 ? `${minors1}` : undefined,
      old: old1 ? `${old1}` : undefined
    };
  }
  updateSearch = data1 =&gt; {
    let {
      packageName: packages1,
      showMinors: minors1,
      showOld: old1
    } = data1;
    this.router.transitionTo('query', {
      queryParams: {
        packages: packages1,
        minors: minors1,
        old: old1
      }
    });
  };
}

//# sourceURL=webpack://package-majors/./components/search.ts?</code></pre>
<p></details></p>
<h3 id="with-devtool-source-map-1">with <code>devtool: 'source-map'</code></h3>
<p>All the TypeScript that was authored is <em>very</em> close to original source, with the main differences being minor line breaks and spacing changes.</p>
<p><img src="/images/sourcemaps/class-source-map.png" alt="example with devtool set to 'source-map' paused on line 52, showing original source for the getter" /></p>
<p><details><summary>The code from the screenshot</summary></p>
<pre><code class="ts language-ts">import { template } from "@ember/template-compiler";
import Component from '@glimmer/component';
import { fn } from '@ember/helper';
import { service } from '@ember/service';
import { Form } from 'ember-primitives';
import { NameInput } from './name-input';
import { ShowMinors } from './show-minors';
import { ShowOld } from './show-old';
import type RouterService from '@ember/routing/router-service';
import type { DownloadsResponse } from 'package-majors/types';
/**
 * This is triggered on every value change,
 * which we don't need for this app.
 * The early return makes it a normal submit,
 * so the &lt;Form&gt; abstraction is basically just doing the
 * `FormData` conversion for us.
 */ function handleSubmit(onChange1: (data: SearchFormData) =&gt; void, data1: unknown, eventType1: string) {
    if (eventType1 !== 'submit') return;
    onChange1(data1 as SearchFormData);
}
interface SearchFormData {
    packageName: string;
    showMinors?: 'on';
    showOld?: 'on';
}
export class Search extends Component&lt;{
    Blocks: {
        default: [data: DownloadsResponse];
    };
}&gt; {
    static{
        template(`
    &lt;Form @onChange={{fn handleSubmit this.updateSearch}}&gt;
      &lt;NameInput @value={{this.last.packages}} /&gt;

      &lt;ShowMinors checked={{this.last.minors}} /&gt;
      &lt;ShowOld checked={{this.last.old}} /&gt;
    &lt;/Form&gt;
  `, {
            component: this,
            eval () {
                return eval(arguments[0]);
            }
        });
    }
    @service
    router: RouterService;
    get last() {
        let qps1 = this.router.currentRoute?.queryParams;
        let minors1 = qps1?.['minors'];
        let packages1 = qps1?.['packages'];
        let old1 = qps1?.['old'];
        return {
            packages: packages1 ? `${packages1}` : '',
            minors: minors1 ? `${minors1}` : undefined,
            old: old1 ? `${old1}` : undefined
        };
    }
    updateSearch = (data1: SearchFormData)=&gt;{
        let { packageName: packages1, showMinors: minors1, showOld: old1 } = data1;
        this.router.transitionTo('query', {
            queryParams: {
                packages: packages1,
                minors: minors1,
                old: old1
            }
        });
    };
}</code></pre>
<p></details></p>]]></description><link>https://nullvoxpopuli.com/2023-12-19-hifi-sourcemap</link><guid isPermaLink="true">https://nullvoxpopuli.com/2023-12-19-hifi-sourcemap</guid><pubDate>Tue, 19 Dec 2023 16:56:17 GMT</pubDate></item><item><title><![CDATA[Template Only vs Class Components]]></title><description><![CDATA[<p>Folks often hear that template-only components are faster than class-based components. But is it true?</p>
<p>Here is the setup,</p>
<ul>
<li><p>the <a href="https://github.com/krausest/js-framework-benchmark/tree/master/frameworks/keyed/ember">js-framework-benchmark</a> app.</p>
<ul>
<li>this benchmark focuses on testing the rendering of large tables, measuring CPU, Memory, etc while also measuring time to first render as well as time to update across certain common behaviors of these large tables.<br />
Normally, Large tables aren't something you want in a real app -- almost always you want pagination, and/or virtualized content.<br />
However, this is a decent stress test for "large amount of data rendered at once".</li></ul></li>
<li><p>two copies of the code with the "row" content extracted to its own component.</p>
<ul>
<li>the template-only version of the component looks like this</li></ul>
<pre><code class="js language-js">   const Row =
     &lt;template&gt;
       &lt;tr class={{if @isSelected 'danger'}}&gt;
         &lt;td class='col-md-1'&gt;{{@item.id}}&lt;/td&gt;
         &lt;td class='col-md-4'&gt;&lt;a {{on 'click' @select}}&gt;{{@item.label}}&lt;/a&gt;&lt;/td&gt;
         &lt;td class='col-md-1'&gt;&lt;a {{on 'click' @remove}}&gt;&lt;span
               class='glyphicon glyphicon-remove'
               aria-hidden='true'
             &gt;&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
         &lt;td class='col-md-6'&gt;&lt;/td&gt;
       &lt;/tr&gt;
     &lt;/template&gt;;</code></pre>
<ul>
<li>the class-version of the component looks like this:</li></ul>
<pre><code class="js language-js">   class Row extends Component {
     &lt;template&gt;
       &lt;tr class={{if @isSelected 'danger'}}&gt;
         &lt;td class='col-md-1'&gt;{{@item.id}}&lt;/td&gt;
         &lt;td class='col-md-4'&gt;&lt;a {{on 'click' @select}}&gt;{{@item.label}}&lt;/a&gt;&lt;/td&gt;
         &lt;td class='col-md-1'&gt;&lt;a {{on 'click' @remove}}&gt;&lt;span
               class='glyphicon glyphicon-remove'
               aria-hidden='true'
             &gt;&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
         &lt;td class='col-md-6'&gt;&lt;/td&gt;
       &lt;/tr&gt;
     &lt;/template&gt;
   }</code></pre></li>
</ul>
<p>The delta between the two components is this:</p>
<pre><code class="diff language-diff">-  const Row =
+  class Row extends Component {
    &lt;template&gt;
      &lt;tr class={{if @isSelected 'danger'}}&gt;
        &lt;td class='col-md-1'&gt;{{@item.id}}&lt;/td&gt;
        &lt;td class='col-md-4'&gt;&lt;a {{on 'click' @select}}&gt;{{@item.label}}&lt;/a&gt;&lt;/td&gt;
        &lt;td class='col-md-1'&gt;&lt;a {{on 'click' @remove}}&gt;&lt;span
              class='glyphicon glyphicon-remove'
              aria-hidden='true'
            &gt;&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
        &lt;td class='col-md-6'&gt;&lt;/td&gt;
      &lt;/tr&gt;
-    &lt;/template&gt;;
+    &lt;/template&gt;
+  }</code></pre>
<h2 id="why">Why?</h2>
<p>When using TypeScript a question came up about the ergonomics of typing either of these components.</p>
<p>Here is what typing the template-only component looks like:</p>
<pre><code class="ts language-ts">import { TOC } from '@ember/component/template-only';

interface Signature {
  Args: {
    item: { id: string; label: string };
    select: () =&gt; void;
    remove: () =&gt; void;
  }
}

const Row: TOC&lt;Signature&gt; = &lt;template&gt; ... &lt;/template&gt;;</code></pre>
<p>And here is what typing the class component looks like</p>
<pre><code class="ts language-ts">interface Signature {
  Args: {
    item: { id: string; label: string };
    select: () =&gt; void;
    remove: () =&gt; void;
  }
}

// The Component was already imported from `@glimmer/component`
class Row extends Component&lt;Signature&gt; { ... };</code></pre>
<p>This post isn't about the ergonomics of each of these, or which I prefer, but this is why I wanted to get actual hard data about the claims we've been hearing for years about template-only vs class-based.</p>
<p><em>But!</em> <em>having</em> to make make a <code>const</code> for the default-export case, <em>is</em> extra work, as you <em>must</em> make a <code>const</code>. However, in a fully gjs/gts-using app, there does not need to be any default exports at as, as there would be no loose mode (classic, hbs files).<br />
Using <a href="https://github.com/discourse/ember-route-template">gjs / gts in routes</a>, your regular component files with one export could look like:</p>
<pre><code class="ts language-ts">// app/components/my-component.gts
export const MyComponent: TOC&lt;Signature&gt; = &lt;template&gt;...&lt;/template&gt;;</code></pre>
<p>and then imported:</p>
<pre><code class="ts language-ts">import Route from 'ember-route-template';
import { MyComponent } from 'my-app/components/my-component';

export default Route(
  &lt;template&gt;
      &lt;MyComponent&gt;
          {{outlet}}
      &lt;/MyComponent&gt;
  &lt;/template&gt;
);</code></pre>
<p>But anyway, getting back to answering the question I've yet to mention, </p>
<blockquote>
  <p>if someone prefers empty class components (which are linted against) for usage in TypeScript, is there anything they're missing out on by not using template-only components?</p>
</blockquote>
<p>The tl;dr: is <em>yes</em>. But actual results will vary based on your actual app and use cases.</p>
<h2 id="_template-only-components-are-7-26-faster_"><em>template-only components are 7-26% faster</em></h2>
<p>(for this particular benchmark and depending on which thing you're measuring).</p>
<p>As always, please test in your own apps. <br />
<a href="https://www.tracerbench.com/">Tracerbench</a> is a statistically sound tool that uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark">the built in performance.mark()</a> apis.</p>
<p>This does <strong>not</strong> mean that this is always going to be the case. There has been increased activity in work happening in the VM to speed things up. For example, one such option that is being investigated is avoiding destruction callbacks on class components if the class didn't implement a destruction method (as well as removing the parent node before runnig destruction on child nodes (this would improve rendering brand new content or navigating to a new page)). Also at the time of writing, there already exists a good number of perf-related PRs, if anyone is interested: <a href="https://github.com/glimmerjs/glimmer-vm/pulls?q=is%3Apr+perf+">on the glimmer-vm repo</a></p>
<p>For the js-framework-benchmark, here are the actual results.</p>
<p><img src="/images/template-only-vs-class-js-framework-benchmark/results.png" alt="Image of the results (table below)" /></p>
<p>And the table of the results here:</p>
<style>
  /* override blog styles */
  .results__table td {
    background-image: none !important;
  }
  .results__table h3 {
    font-size: 1rem;
  }
  .results__table th {
    line-height: 1.25rem;
    text-transform: initial !important;
    max-width: 200px;
    font-weight: normal !important;
  }
  .results__table .benchname {
  }
  .results__table button {
    padding: 0;
    border: none;
    color: blue;
    display: block;
    background-color: unset;
  }
  .results__table .rowCount {
    word-wrap: break-word;
    white-space: break-spaces;
    line-height: 1.15rem;
    max-width: 200px;
    font-weight: normal;
    text-transform: initial;
  }
</style>
<table class="results__table" inert>
    <thead>
        <tr>
            <td class="description">
                <h3>Duration in milliseconds ± 95% confidence interval (Slowdown = Duration / Fastest)</h3>
            </td>
        </tr>
    </thead>
    <thead>
        <tr>
            <th class="benchname"><button class="button button__text ">Name</button><br>Duration for...</th>
            <th><a target="_blank" rel="noreferrer" href="https://emberjs.com/">template-only</a></th>
            <th><a target="_blank" rel="noreferrer" href="https://emberjs.com/">with classes</a></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="benchname">
                <button class="button button__text ">create rows</button>
                <div class="rowCount">creating 1,000 rows (5 warmup runs).</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">87.1</span><span class="deviation">3.3</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(117, 196, 125); color: rgb(0, 0, 0);"><span class="mean">97.2</span><span class="deviation">2.3</span><br><span class="factor">(1.12)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">replace all rows</button>
                <div class="rowCount">updating all 1,000 rows (5 warmup runs).</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">107.9</span><span class="deviation">1.5</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(124, 198, 125); color: rgb(0, 0, 0);"><span class="mean">125.0</span><span class="deviation">2.0</span><br><span class="factor">(1.16)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">partial update</button>
                <div class="rowCount">updating every 10th row for 1,000 rows (3 warmup runs). 4 x CPU slowdown.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">27.2</span><span class="deviation">0.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(110, 194, 125); color: rgb(0, 0, 0);"><span class="mean">29.0</span><span class="deviation">1.3</span><br><span class="factor">(1.07)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">select row</button>
                <div class="rowCount">highlighting a selected row. (5 warmup runs). 4 x CPU slowdown.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">26.4</span><span class="deviation">0.5</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(131, 200, 126); color: rgb(0, 0, 0);"><span class="mean">31.8</span><span class="deviation">1.1</span><br><span class="factor">(1.21)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">swap rows</button>
                <div class="rowCount">swap 2 rows for table with 1,000 rows. (5 warmup runs). 4 x CPU slowdown.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">34.9</span><span class="deviation">1.5</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(112, 195, 125); color: rgb(0, 0, 0);"><span class="mean">37.8</span><span class="deviation">2.4</span><br><span class="factor">(1.08)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">remove row</button>
                <div class="rowCount">removing one row. (5 warmup runs). 2 x CPU slowdown.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">33.5</span><span class="deviation">1.3</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(116, 196, 125); color: rgb(0, 0, 0);"><span class="mean">37.2</span><span class="deviation">2.0</span><br><span class="factor">(1.11)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">create many rows</button>
                <div class="rowCount">creating 10,000 rows. (5 warmup runs with 1k rows).</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">808.8</span><span class="deviation">6.6</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(105, 193, 124); color: rgb(0, 0, 0);"><span class="mean">840.7</span><span class="deviation">8.5</span><br><span class="factor">(1.04)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">append rows to large table</button>
                <div class="rowCount">appending 1,000 to a table of 1,000 rows.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">107.2</span><span class="deviation">1.7</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(106, 193, 124); color: rgb(0, 0, 0);"><span class="mean">112.1</span><span class="deviation">1.9</span><br><span class="factor">(1.05)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">clear rows</button>
                <div class="rowCount">clearing a table with 1,000 rows. 4 x CPU slowdown. (5 warmup runs).</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">38.5</span><span class="deviation">1.3</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(139, 203, 126); color: rgb(0, 0, 0);"><span class="mean">48.3</span><span class="deviation">2.0</span><br><span class="factor">(1.26)</span></td>
        </tr>
        <tr>
            <th><button class="button button__text sort-key">weighted  geometric mean</button>of all factors in the table</th>
            <th style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);">1.00</th>
            <th style="background-color: rgb(116, 196, 125); color: rgb(0, 0, 0);">1.11</th>
        </tr>
    </tbody>
    <tbody>
        <tr>
            <th class="benchname">
                <button class="button button__text ">consistently interactive</button>
                <div class="rowCount">a pessimistic TTI - when the CPU and network are both definitely very idle. (no more CPU tasks over 50ms)</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">4,285.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">4,279.7</span><br><span class="factor">(1.00)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">total kilobyte weight</button>
                <div class="rowCount">network transfer cost (post-compression) of all the resources loaded into the page.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">573.1</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">573.1</span><br><span class="factor">(1.00)</span></td>
        </tr>
        <tr>
            <th><button class="button button__text "> geometric mean</button>of all factors in the table</th>
            <th style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);">1.00</th>
            <th style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);">1.00</th>
        </tr>
    </tbody>
    <thead>
        <tr>
            <td class="description">
                <h3>Memory allocation in MBs ± 95% confidence interval</h3>
            </td>
        </tr>
    </thead>
    <thead>
        <tr>
            <th class="benchname"><button class="button button__text ">Name</button></th>
            <th>template-only</th>
            <th>with classes</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="benchname">
                <button class="button button__text ">ready memory</button>
                <div class="rowCount">Memory usage after page load.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">6.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">6.8</span><br><span class="factor">(1.00)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">run memory</button>
                <div class="rowCount">Memory usage after adding 1,000 rows.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">13.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(110, 194, 125); color: rgb(0, 0, 0);"><span class="mean">14.7</span><br><span class="factor">(1.07)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">update every 10th row for 1k rows (5 cycles)</button>
                <div class="rowCount">Memory usage after clicking update every 10th row 5 times</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">13.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(110, 194, 125); color: rgb(0, 0, 0);"><span class="mean">14.8</span><br><span class="factor">(1.07)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">creating/clearing 1k rows (5 cycles)</button>
                <div class="rowCount">Memory usage after creating and clearing 1000 rows 5 times</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">8.1</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(107, 193, 124); color: rgb(0, 0, 0);"><span class="mean">8.6</span><br><span class="factor">(1.05)</span></td>
        </tr>
        <tr>
            <th class="benchname">
                <button class="button button__text ">run memory 10k</button>
                <div class="rowCount">Memory usage after adding 10,000 rows.</div>
            </th>
            <td style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);"><span class="mean">70.8</span><br><span class="factor">(1.00)</span></td>
            <td style="background-color: rgb(118, 196, 125); color: rgb(0, 0, 0);"><span class="mean">79.5</span><br><span class="factor">(1.12)</span></td>
        </tr>
        <tr>
            <th><button class="button button__text "> geometric mean</button>of all factors in the table</th>
            <th style="background-color: rgb(99, 191, 124); color: rgb(0, 0, 0);">1.00</th>
            <th style="background-color: rgb(109, 194, 125); color: rgb(0, 0, 0);">1.06</th>
        </tr>
    </tbody>
</table>
<p>My system at the time of running the benchmark(s):</p>
<pre><code class="bash language-bash">❯ google-chrome --version
Google Chrome 120.0.6099.109 

❯ uname -srp
Linux 6.2.0-39-generic x86_64


❯ lsb_release -csd
Ubuntu 23.04
lunar</code></pre>
<p>info from <code>screenfetch</code>:</p>
<pre><code>CPU: AMD Ryzen 9 7900X 12-Core @ 24x 4.7GHz
GPU: NVIDIA GeForce RTX 4080
RAM: &lt;in use&gt; / 63438MiB</code></pre>]]></description><link>https://nullvoxpopuli.com/2023-12-20-template-only-vs-class-components</link><guid isPermaLink="true">https://nullvoxpopuli.com/2023-12-20-template-only-vs-class-components</guid><pubDate>Wed, 20 Dec 2023 19:00:38 GMT</pubDate></item><item><title><![CDATA[What do TC39 Signals mean for you, specifically?]]></title><description><![CDATA[<p>⚠️ If I've missed anything, or am wrong about something, please let me know in the comments ❤️</p>
<p>Quick Links:</p>
<ul>
<li><a href="https://jsbin.com/safoqap/edit?html,output">JSBin</a> to play around. 🥳</li>
<li><a href="https://github.com/tc39/proposal-signals">The Proposal</a></li>
<li><a href="https://github.com/proposal-signals/signal-polyfill">The Polyfill</a></li>
<li><a href="https://github.com/proposal-signals/signal-utils/">Library using the polyfill</a></li>
<li><a href="https://signals.nullvoxpopuli.com/">Interactive Tutorial</a></li>
</ul>
<p>tl;dr:</p>
<ul>
<li>Signals enables many ecosystems to start interoping together, even if they are completely isolated today. </li>
</ul>
<p>What you can do?</p>
<ul>
<li>Get involved, try out the polyfill, try to integrate it in to you framework (in abranch) ✨</li>
</ul>
<hr />
<p>Signals have been <a href="https://github.com/tc39/proposal-signals">proposed to TC39</a> to be built in to JavaScript as a language native feature. </p>
<p>But what does this actually mean for you and your daily work?</p>
<ol>
<li><a href="#whoisthisfor">Who is this for?</a></li>
<li><a href="#whyamitalkingaboutthis">Why am I talking about this?</a></li>
<li><a href="#whatevenaresignals">What even are Signals?</a></li>
<li><a href="#whyshouldicare">Why should I care?</a></li>
<li><a href="#whataretherequirements">What are the requirements?</a></li>
<li><a href="#seemsliketherearemissingfeatures">Seems like there are missing features 🤔</a></li>
<li><a href="#howthisaffectsyou">How this affects you</a><ol>
<li><a href="#libraryauthoring">library authoring</a></li>
<li><a href="#frameworkmaintenance">framework maintenance</a></li>
<li><a href="#applicationdev">application dev</a></li></ol></li>
<li><a href="#howyoucangetinvolved">How you can get involved</a></li>
</ol>
<h2 id="who-is-this-for">Who is this for?</h2>
<style>
.post-content details {
  align-self: start;
}
</style>
<p>There are generally three types of roles a person can play when it comes to programming -- and these may seem obvious, but it's important that we be on the same page, because it's easy (especially for me!) to forget which audience we're talking about in which situations -- feel free to skip to the next section:</p>
<p><strong>Framework authors</strong></p>
<p>These are the people who will jump through any hoop in the name of ergonomics, performance, compatibility, you name it. These people work directly on projects like <a href="https://angular.dev/">Angular</a>, <a href="https://emberjs.com/">Ember</a>, <a href="preactjs.com/">Preact</a>, <a href="https://www.solidjs.com/">Solid</a>, <a href="https://svelte.dev/">Svelte</a>, <a href="https://vuejs.org/">Vue</a>, and many more.</p>
<p><strong>Library authors</strong></p>
<p>These are people who may want their library to be usable in multiple / all frameworks that are supporting Signals -- traditionally in order to have broad reach across every framework, libraries would need to either have no reactivity, or private reactivity -- which isn't integrated into the host application, and in either case, if these libraries render anything, the host framework must be hands off, as the library "takes over" the dom element that it is given and <em>multiple renderers cannot control the same subtree of the DOM</em> -- this is common in Charting libraries, or older jQuery based libraries.  Some libraries could provide an abstraction over reactivity and then <em>adapters</em> for each reactivity system -- Vue, Svelte, Angular, etc.  </p>
<p><strong>Application developers</strong></p>
<p>These people <em>could</em> use the TC39 Signals directly, but more likely that their framework or ecosystem used for their application will provide wrapper utilities that align with the framework/ecosystem's existing nomenclature and mental models. For example, <a href="https://svelte.dev/blog/runes">Svelte has runes</a>, and <a href="https://vuejs.org/guide/essentials/reactivity-fundamentals.html">Vue has ref</a>, and <a href="https://api.emberjs.com/ember/release/functions/@glimmer%2Ftracking/tracked">Ember has tracked</a> -- all of these can <em>wrap</em> the TC39 Signals, and in a <em>minor, non-breaking, version upgrade</em> of your framework's core library, could suddenly have support for Signals and the broader reactive ecosystem -- and you wouldn't even know without reading the changelog! 🎉</p>
<p></details></p>
<h2 id="why-am-i-talking-about-this">Why am I talking about this?</h2>
<p>I, <a href="https://linktr.ee/nullvoxpopuli">@NullVoxPopuli</a>, am <em>very excited</em> about Reactivity[^my-writing].</p>
<p>There has been a problem in JavaScript since the dawn of time (<a href="https://en.wikipedia.org/wiki/JavaScript">1995</a>): as ecosystems develop (and specifically what I am presently concerned about: web apps (which, kind of, moves our dawn of time to the mid 2000s)), they cannot share code between each other. React can't share code with Vue, nor can Svelte share code with Vue, or Ember, Angular, Solid, etc.  </p>
<p><em>Billions of lines of code have been duplicated across these ecosystems <em>solely</em>[^solely-duplication] because the reactive systems are separate</em>.</p>
<p>There are 3 major categories for which I can place my excitements:</p>
<ol>
<li><strong>Interoperability</strong>, solving the above-described problem</li>
<li><strong>Technically</strong>, the way the reactive algorithm works (even as the TC39 proposal changes over time) is super exciting, and something I've been using myself for years in Ember.</li>
<li><strong>Adoption</strong> - Non-breaking, non-major change<a href="#footnote-editions"><sup>[editions]</sup></a> to introduce Signal compatibility to a framework. </li>
</ol>
<p>It's intended for framework and library authors to swap out their internals with the proposal's primitives (shedding tons of kb, and <em>beginning the <strong>Epic Quest</strong></em> of aligning on all the similarities between our frameworks - eliminating (probably) <a href="https://simple.wikipedia.org/wiki/Petabyte">Petabytes</a> of network transfer of (basically) duplicate code each year).</p>
<h2 id="what-even-are-signals">What even are Signals?</h2>
<p>Signals are a means to enable efficient computation of state for a particular viewer. The viewer in many cases is the DOM, but could extend to any observable[^observable] boundary, a WebSocket, Database Connection, etc.</p>
<p>The exact way in which this works isn't important right now (and also the exact details will probably change over time as the proposal is still in <a href="https://tc39.es/process-document/">Stage 1</a>) -- perhaps a topic for another time!</p>
<h3 id="some-helpful-terminology-and-definitions-that-various-folks-use">Some helpful terminology and definitions that various folks use</h3>
<p>As people talk about Signals, auto-tracking, Runes, or other forms of reactivity, it may be heplful to have everything defined / so we can all be on the same page / reduce any potential ambiguity or uncertainty. Not all of these will be used here, in this post, but… more <em>for your (and my own) information[^terms]</em>.</p>
<table>
<thead>
<tr>
<th id="term">term</th>
<th id="meaning">meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>node</td>
<td>part of the reactive graph</td>
</tr>
<tr>
<td>value</td>
<td>an alias of node</td>
</tr>
<tr>
<td>cell</td>
<td>could be an alias of node, but is often root state, part of the spreadsheet analogy</td>
</tr>
<tr>
<td>root state</td>
<td>a value representing state, it cannot depend on anything within the graph, but any number of other nodes in the reactive graph may depend on it. This is different from a render root, which would be the top element-node of your app.</td>
</tr>
<tr>
<td>atom</td>
<td>an alias of root state</td>
</tr>
<tr>
<td>derived data</td>
<td>a derivation on or of state(s), which may or may not be cached, in a pure form this could be written as <code>derived = f(state)</code></td>
</tr>
<tr>
<td>computed</td>
<td>alias of derived, tho this has had different meanings throughout the years in different ecosystems. In this context, specific to <a href="https://github.com/tc39/proposal-signals">the proposal</a>, it is a cached derived value.</td>
</tr>
<tr>
<td>formula</td>
<td>alias of derived, part of the spreadsheet analogy</td>
</tr>
<tr>
<td>upstream</td>
<td>the node that is "upstream of" a given node is closer to the root.</td>
</tr>
<tr>
<td>downstream</td>
<td>the node that is "downstream of" a given node is further away from the root.</td>
</tr>
<tr>
<td>sink</td>
<td>a consumer of sources or values</td>
</tr>
<tr>
<td>source</td>
<td>root state, the source of a value</td>
</tr>
<tr>
<td>auto-tracking</td>
<td>the process of determining which nodes a computed or derived node depends on by running it and recording which other nodse were read</td>
</tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy evaluation</a></td>
<td>work is only performed when needed</td>
</tr>
<tr>
<td>steady state</td>
<td>re-reading derived values or root state does not cause a change in output</td>
</tr>
<tr>
<td>invalidated</td>
<td>a node in the reactive graph is marked as dirty</td>
</tr>
<tr>
<td>dirty</td>
<td>a node in the reactive graph will be re-evaluated next time it is read</td>
</tr>
<tr>
<td>settled</td>
<td>alias of settled state</td>
</tr>
<tr>
<td>consumed</td>
<td>a derived node has, during evaluation, encountered nodes to depend on</td>
</tr>
<tr>
<td>effect</td>
<td>a "side-effect" running code when something changes -- in practice this has many definitions and semantics</td>
</tr>
<tr>
<td>entangled</td>
<td>a node in the reactive graph is depends on another node in that same graph, part of the quantum physics analogy</td>
</tr>
<tr>
<td><strong>push</strong></td>
<td>an update strategy initiated by upstream nodes, which tell downsteam subscribers that they changed.</td>
</tr>
<tr>
<td><strong>pull</strong></td>
<td>an update strategy initiated by downstream nodes, which ask upstream dependencies whether or not they changed -- i.e.: when updating a value, the fact of that change is not realized unless the "renderer" accesses that value.</td>
</tr>
<tr>
<td>Cached</td>
<td>the computation or function will not run again unless the consumed values within change</td>
</tr>
<tr>
<td>Runes</td>
<td>What Svelte calls their Signals</td>
</tr>
<tr>
<td>Tracked properties</td>
<td>What Ember calls their Signals</td>
</tr>
<tr>
<td>refs</td>
<td>What Vue calls their reactive values</td>
</tr>
</tbody>
</table>
<p>To continue the quantum physics analogy:</p>
<blockquote>
  <p>A signal graph is in a superposition of states, but you can't tell because the second you try to observe them the wave collapses</p>
  <p>but you can create double slit experiments. </p>
</blockquote>
<p><em><a href="https://twitter.com/wycats">wycats</a></em>.</p>
<h2 id="why-should-i-care">Why should I care?</h2>
<p>From <a href="https://discord.com/channels/1224208557126193333/1226244045689327769/1227374931168464957">gbj in the Discord</a></p>
<blockquote>
  <p>the proposal is designed to be the greatest common factor of the existing mainstream framework signal approaches, not the least common denominator</p>
</blockquote>
<blockquote>
  <p>It’s “how much could we share?” not “how little can we get away with?”</p>
</blockquote>
<p>Imagine, a reactive library could be built in a such a way, using native Signals, that it works across UI Frameworks: Vue, Svete, Ember, (etc), Node CLI applications, databases, websocket implementations, etc. This proposal has the opportunity to dramatically reduce the amount of duplicate code we have between our different JavaScript ecosystems.</p>
<p>Some libraries that I think could greatly benefit from a shared signal implementation (provided target frameworks also natively integrate with native signals):</p>
<ul>
<li><a href="https://tanstack.com/">TanStack</a> family of projects.</li>
<li>Charting libraries, such as <a href="https://d3js.org/">D3.js</a>.</li>
<li>Any project using web components.</li>
</ul>
<p>I don't have data, as this would be very hard to measure, but I like guessing, so <em>I'm guessing</em>: if we had standard / platform / native signals 6 years ago, we could have saved at least a trillion dollars in duplicate library development consts, research and development, etc. Now, none of this proposal would be possible without those 6+ years of research, exploration, collaboration, and trying to out-do one another 😉.</p>
<p>One thing that will be interesting longer term is how <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">web components</a> could maybe one day be ergonomic to author without the use of a wrapper library (<a href="https://lit.dev/">Lit</a>, for example (which helps fix some of web components' ergonomics issues)).</p>
<p>There are two places to watch for developments there:</p>
<ul>
<li><a href="https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md">HTML Template Instantiation</a></li>
<li><a href="https://github.com/WICG/webcomponents/pull/1023">DOM Parts</a></li>
</ul>
<h2 id="what-are-the-requirements">What are the requirements?</h2>
<ul>
<li><a href="https://github.com/proposal-signals/proposal-signals/issues/161">Autotracking</a> - the main feature of the Signals proposal that makes it more than just values and functions.</li>
<li>Memory allocations should be kept to a minimum.</li>
<li><code>Signal.State</code> should be extensible with fewer object allocations so that frameworks can add their own functionality on top.</li>
<li>multiple types of effects and batching should be implementable by framework authors.</li>
<li>Writes are synchronous, and immediately take effect. At no point in the reactive graph would reading a descendent, deriving from some ancestor, be out of sync.</li>
</ul>
<h2 id="what-are-the-considerations">What are the considerations?</h2>
<p>This is just a snippet of a much bigger list of goals, considerations, and desires for the Signals proposal:</p>
<ul>
<li>The proposal is minimal, and intended to be added on to later -- Changes to a language take a lot of time, so like a pull request, the smaller it is, the higher the chance it will progress towards being accepted.  </li>
<li>Computation must be glitch-free, so no unnecessary calculations are ever performed.</li>
<li>JS Frameworks have their own scheduling, and the native signals implementation should enable that. There is likely no way to write a "scheduler" that efficiently integrates with a framework's own dynamic tree better than the framework native scheduling.</li>
<li>Enable composition of different codebases which use Signals -- aka / cross-framework library sharing.</li>
</ul>
<p>There are many more of these, and if you want to learn more about this, check out the <a href="https://github.com/tc39/proposal-signals?tab=readme-ov-file#design-goals-for-signals">Design goals for Signals</a>.</p>
<h2 id="seems-like-there-are-missing-features-🤔">Seems like there are missing features 🤔</h2>
<p>The proposal, at the time of writing, is still in <a href="https://tc39.es/process-document/">Stage 1</a>, so just about everything is subject to change and nothing is set in stone.  </p>
<p>If you want to help out with the proposal, please <a href="https://github.com/tc39/proposal-signals">open an issue or PR for collaboration</a> 🎉</p>
<h2 id="how-this-affects-you">How this affects you</h2>
<h3 id="library-authoring">library authoring</h3>
<p>Here is a demo of <a href="https://www.youtube.com/watch?v=HSVcZa5yTKE&pp=ygUPamFjayBoZXJyaW5ndG9u">Signals in React and Svelte by Jack Herrington</a> -- showing that we can make cross-framework libraries.</p>
<ul>
<li>reactive code using what's in the proposal</li>
<li>no need for abstractions</li>
<li>must subscribe to "derived" purism (no effects).</li>
</ul>
<p>In universal code, we can't use effects, because an effect usually has framework-specific semantics that don't make sense in other frameworks -- for example, if you use React's effect, the whole library may as well be react-specific.<br />
As a library author, you could circumvent this restriction by requiring your users to call a destructor at time that makes sense for your library, which calls the <code>Watcher</code>'s <code>unwatch</code> method.</p>
<ul>
<li>you can use impure computeds, like the <a href="https://github.com/NullVoxPopuli/signal-utils/?tab=readme-ov-file#async-function"><code>asyncFunction</code></a> <a href="https://github.com/NullVoxPopuli/signal-utils/blob/main/src/async-function.ts">implementation</a> does (mentioned above).</li>
</ul>
<p>example usage:</p>
<pre><code class="jsx language-jsx">import { Signal } from 'signal-polyfill';
import { signalFunction } from 'signal-utils/async-function';

const url = new Signal.State('...');
const signalResponse = signalFunction(async () =&gt; {
  const response = await fetch(url.get()); // entangles with `url`
  // after an away, you've detatched from the signal-auto-tracking
  return response.json(); 
});

// output: true
// after the async function completes
// output: false
&lt;template&gt;
  &lt;output&gt;{{signalResponse.isLoading}}&lt;/output&gt;
&lt;/template&gt;</code></pre>
<p>Aonther util that enables more powerful fetch / <code>AbortController</code> management, is the <em><code>Relay</code></em>. (See <a href="https://www.pzuraq.com/blog/on-signal-relays">Signal Relays</a> by @<a href="https://twitter.com/pzuraq">pzuraq</a>).</p>
<pre><code class="js language-js">export const fetchJson = (url) =&gt; {
  return new Signal.Relay(
    { isLoading: false, value: undefined },
    (set, get) =&gt; {
      let controller;

      const loadData = async () =&gt; {
        controller?.abort();

        set({ ...get(), isLoading: true });

        controller = new AbortController();
        const response = await fetch(url.get(), { signal: controller.signal });
        const value = await response.json();

        set({ ...get(), value, isLoading: false });
      }

      loadData();

      return {
        update: () =&gt; loadData(),
        destroy:() =&gt; controller?.abort(),
      }
    }
  );
}</code></pre>
<p>The Relay pattern provides a way to have more control over the behavior of things-with-lifetime, such as a fetch request. Definitely checkout <a href="https://www.pzuraq.com/blog/on-signal-relays">pzuraq's blog post for more details</a>. </p>
<p>For now, there is an exploratory <a href="https://github.com/proposal-signals/signal-utils/">signal-utils</a> library with a bunch of reactive utilities and data structures -- pull requests are very encouraged here!! It'd be equally awesome to see other experiments people come up in their own libraries! (Noting that we don't want to accidentally make a new framework, but moreso want to test out the capabilities of the polyfill / current design of the proposal and validate ergonomics / problems / etc).</p>
<h3 id="framework-maintenance">framework maintenance</h3>
<p>The dream is that once frameworks integrate with native signals, whole ecosystems begin to start being able to share resources with one another. </p>
<p>Especially as it is still the early days, the most useful thing now with respect to the proposal is to try out the polyfill in your framework, replacing your own reactive primitives with those from the <a href="https://github.com/proposal-signals/signal-polyfill">polyfill</a> and open issues such as:</p>
<ul>
<li><a href="https://github.com/proposal-signals/signal-polyfill/issues/2">Batch unsubscribe issue</a></li>
<li><a href="https://github.com/tc39/proposal-signals/issues/216">calling watch on computed signal can prevent getting correct latest value</a></li>
<li><a href="https://github.com/tc39/proposal-signals/issues/201">False positive in cycle detection</a></li>
<li>and more!! folks are already doing a great job testing things out!</li>
</ul>
<p>Here is an example of <a href="https://github.com/lifeart">@lifeart</a>'s spike to add the signal polyfill to their renderer[^glimmer-next]: <a href="https://github.com/lifeart/glimmer-next/pull/114">glimmer-next#114</a>, and in doing so, a problem was found where <a href="https://github.com/tc39/proposal-signals/issues/215"><code>unwatch</code> performance worsened by ~ 3000%</a> (oops).</p>
<p>Effects and fine-grained rendering can be implemented via a watched <code>Computed</code> like here in <a href="https://github.com/lifeart/glimmer-next/pull/114/files#diff-15c9f7b9618cbbf23b072333b9f93d40bbc409eb188ef9be0adc3674803b7f18L95">glimmer-next</a> -- this whole PR is probably a good reference for the amount of work involved in swapping the core reactive primitives. </p>
<p>Higher level abstractions should also be possible, and some of these are explored in <a href="https://github.com/proposal-signals/signal-utils/">signal-utils</a>. For example, in ecosystems that encourage classes, they may implement a decorator which allows "just regular property access" to continue working:</p>
<pre><code class="jsx language-jsx">import { signal } from 'signal-utils';

class State {
    @signal accessor #value = 3;

    get doubled() {
        return this.#value * 2;
    }

    increment = () =&gt; this.#value++;
}

let state = new State();


// output: 6
// button clicked
// output: 8
&lt;template&gt;
  &lt;output&gt;{{state.doubled}}&lt;/output&gt;
  &lt;button onclick={{state.increment}}&gt;+&lt;/button&gt;
&lt;/template&gt;</code></pre>
<p>Another example, for reference, could be the <a href="https://github.com/proposal-signals/signal-utils/pull/61"><code>reaction()</code></a> utility from <a href="https://mobx.js.org/reactions.html#reaction">MobX</a>.</p>
<h3 id="application-dev">application dev</h3>
<p>For now there isn't much to do <em>directly</em> with the proposal, but you can work with your framework ecosystems to see how you can help test things out and provide feedback! <br />
This isn't to say that you wouldn't be able to use native Signals in your application once everything ships, and is integrated, etc, -- on the contrary! -- but moreso that you'll <em>likely</em> want to stick with your framework's specific nomenclature, utilities, etc.</p>
<h2 id="how-you-can-get-involved">How you can get involved</h2>
<p>On <a href="https://github.com/proposal-signals/proposal-signals/issues/96">this call for participation</a> on the <a href="https://github.com/tc39/proposal-signals">proposal github</a>, you'll find links to </p>
<ul>
<li><a href="https://discord.gg/9yrQ7SN6zW">The Signals Discord Server</a></li>
<li><a href="https://github.com/proposal-signals/proposal-signals/issues/95">Asking for help, and ways you can help</a></li>
</ul>
<p>In summary, many / most contributions are welcome:</p>
<ul>
<li>fixing typos in the README</li>
<li>adding tests</li>
<li>playing with the polyfill (finding bugs, fixing them, etc)</li>
<li>Document use cases for or against certain reactive programming patterns (and if the current design supports those use cases)</li>
<li>Add to the <a href="https://github.com/NullVoxPopuli/signals-tutorial">interactive tutorial here</a><ul>
<li><a href="https://signals.nullvoxpopuli.com/">viewable here</a></li></ul></li>
</ul>
<p>and more! (see the <a href="https://github.com/proposal-signals/proposal-signals/issues/95">linked issue</a>)</p>
<p>You can also try out signals for yourself here at this <a href="https://jsbin.com/safoqap/edit?html,output">JSBin</a>.</p>
<hr />
<p>There is a lot to do!, but we'll get there! Between finding alignment with the broadest group possible, time to implement and adopt, and then adding additional proposals that build on top of the Signals proposal, this is my favorite meme:</p>
<p><img src="/images/plans-measured-in-centuries.png" alt="Screenshot from Dune: Part 2, a Bene Gesserit, reminding us that their plans are measured in centuries" /></p>
<p style="width: 100%; text-align: right">(from Dune: Part 2)</p>
<h2 id="references">References</h2>
<p>…and additional reading:</p>
<ul>
<li><a href="https://tc39.es/process-document/">The TC39 Process</a></li>
<li><a href="https://twitter.com/pzuraq">@pzuraq</a>'s <em><a href="https://discord.com/channels/1224208557126193333/1224208558019842091/1227427776789872692">How Autotracking Works</a></em></li>
<li><a href="https://twitter.com/RyanCarniato">@RyanCarniato</a>'s <em><a href="https://dev.to/this-is-learning/derivations-in-reactivity-4fo1">Derivation in Reactivity</a></em></li>
<li><a href="https://github.com/eisenbergeffect">@eisenbergeffect</a>'s <a href="https://eisenbergeffect.medium.com/a-tc39-proposal-for-signals-f0bedd37a335">A TC39 Proposal for Signals</a></li>
<li>Mat Hostetter's <em><a href="https://skiplang.com/blog/2017/01/04/how-memoization-works.html">Reactivity Overview</a></em></li>
<li>lord's <a href="https://github.com/salsa-rs/salsa/issues/41#issuecomment-589454453">GitHub Comment</a></li>
<li>From <a href="https://github.com/steve8708">@steve8708</a> :</li>
</ul>
<blockquote>
  <p>In my experience, the more you use them, esp as your app scales, the more their DX shines</p>
  <p>Signals are not just about perf<br />
  /1<br />
    <a href="https://twitter.com/Steve8708/status/1629985217116266496">from X/Twitter</a></p>
  <ul>
  <li>From <a href="https://github.com/thdxr">@thdxr</a>, an <a href="https://twitter.com/thdxr/status/1629858176098074625">AMA on X/Twitter</a></li>
  </ul>
</blockquote>
<!-- [gh-starbeam]: https://github.com/starbeamjs/starbeam/ -->
<!-- [x-littledan]: https://twitter.com/littledan -->
<!-- [x-eisenburg]: https://twitter.com/EisenbergEffect -->
<!-- [using-proposal]: https://github.com/tc39/proposal-explicit-resource-management -->
<p><small class="footnote" id="footnote-observable"><a href="#footnote-observable"><sup>[observable]</sup></a>: this definition of observable is simply "noticeable" / "to be noticed" and in no way is intended to correlate to the the programming concept by the same name. Observables as a pattern (rx.js) <em>invert</em> the reactive graph from Signals, and have proven to be less efficient. They are a useful tool!, just not for <em>everything</em> (<a href="https://angular.io/guide/signals">Angular</a> has been <a href="https://dev.to/mfp22/signals-make-angular-much-easier-3k9">moving</a> to <a href="https://medium.com/@michaelfrontend/angular-signals-vs-rxjs-navigating-the-reactive-programming-landscape-946c6dbe79d8">Signals</a> from <a href="https://rxjs.dev/">rx.js</a>, for example).</small></p>
<p>[^my-writing]: But I haven't been the best at communicating -- written, spoken, or otherwise -- about my excitement for reactivity -- a lot of concepts are quite abstract, and a hard to find the right balance of distilling a problem to something digestible verses finding something complex enough where people don't immediately fall in to "but this other way is much easier, or could be done like 'this'".</p>
<p>[^solely-duplication]: maybe not <em>solely</em>, exactly. I'm sure there is a good amount of "I could do better" and "There are bugs, how hard could it be[^how-hard-could-it-be] to do myself"? </p>
<p>[^how-hard-could-it-be]: Anyone else have this genetic trait where they generously undeestimate effort of a task? (not just in programming) .</p>
<p><small class="footnote" id="footnote-editions"><a href="#footnote-editions"><sup>[editions]</sup></a>: See also: <a href="https://emberjs.com/editions/">Editions</a>, an extension to <a href="https://semver.org/">SemVer</a>.</small></p>
<p><small class="footnote" id="footnote-terms"><a href="#footnote-terms"><sup>[terms]</sup></a>: This table alone, with examples in the wild could / should probably be its own post -- it's a lot of information.</small></p>
<p>[^glimmer-next]: this is an alternate DOM renderer for Ember focused on performance, and trying to incrementally gain as much compatibility as possible where it makes sense. I've opened an issue on Ember for a <a href="https://github.com/emberjs/ember.js/issues/20648">Swappable Renderer</a> to help explore what this could be capable of, what compatibility is missing, etc -- it's still early, and the ideas still need to be proved out in big projects. </p>
<p>[^starbeam-note]: Starbeam is not 1.0 yet, and does not presently have a production ready release, so folks should hold off on trying it out. The docs even mismatch a little at this point, so learning it would be a smidge confusing without diving in to the code as the source-of-truth for learning.</p>]]></description><link>https://nullvoxpopuli.com/2024-04-24-signals</link><guid isPermaLink="true">https://nullvoxpopuli.com/2024-04-24-signals</guid><pubDate>Thu, 25 Apr 2024 02:50:42 GMT</pubDate></item><item><title><![CDATA[Effects in Ember]]></title><description><![CDATA[<p>Originally from <a href="https://discuss.emberjs.com/t/how-to-make-an-effect-in-ember/20520?u=nullvoxpopuli">How to make an effect in Ember?</a></p>
<p><a href="https://github.com/trusktr">@trusktr</a> asks:</p>
<blockquote>
  <p>What’s the equivalent of Solid.js <code>createEffect()</code> (or React <code>useEffect()</code>, Meteor <code>Tracker.autorun()</code>, MobX <code>autorun()</code>, Vue <code>watchEffect()</code>, Svelte <code>$effect()</code>, Angular <code>effect()</code>) in Ember.js?</p>
</blockquote>
<p>This is certainly shocking to folks new to ember, but ember deliberately doesn't have an any effect by default.</p>
<p>Now, as a <em>framework</em> author, the concept does  <em>sort of</em> exist (at a high level) -- but I'll circle back around to this in a smidge.</p>
<p>In your Solid demo, if you want to log function calls, you'd do:</p>
<pre><code class="js language-js">const a = () =&gt; 1;
const b = () =&gt; 2;
const c = () =&gt; 3;

&lt;template&gt;
  {{log (a) (b) (c)}}
&lt;/template&gt;</code></pre>
<p>Some notes on function invocation syntax, if needed</p>
<ul>
<li><a href="https://tutorial.glimdown.com/1-introduction/3-transforming-data">Glimmer Tutorial: Transforming Data</a></li>
<li><a href="https://cheatsheet.glimmer.nullvoxpopuli.com/docs/templates#template__notation">https://cheatsheet.glimmer.nullvoxpopuli.com/docs/templates#template__notation</a></li>
</ul>
<p>We use templates as the sole entrypoint to reactivity, whereas solid's reactivity is more general purpose.  With templates, and being DOM focused (<a href="https://github.com/emberjs/ember.js/issues/20648">for now</a>), we can ask ourselves:</p>
<blockquote>
  <p>"If the user can't see the data rendered, does the data need to exist?"</p>
</blockquote>
<p>Now, you're demo (with logging) is definitely effect-y. And if you <em>had no other way</em> (like the situation was somehow impossible to model in a derived data way), you can do this:</p>
<pre><code class="js language-js">function myLog() {
  console.log(a(), b(), c());
}

&lt;template&gt;
  {{ (myLog) }}
&lt;/template&gt;</code></pre>
<p>This would auto-track, so as the consumed tracked data accessed from each of <code>a</code>, <code>b</code>, and <code>c</code> changed, <code>myLog</code> would get to re-run. <br />
However, this has a caveat: data may not be <em>set</em> within <code>myLog</code>, else an infinite render loop would occur.</p>
<p>This is covered here</p>
<ul>
<li><a href="https://tutorial.glimdown.com/2-reactivity/10-synchronizing-external-state">Glimmer Tutorial: <em>synchronizing state</em></a> (to the console in this  case)</li>
</ul>
<p>There is a way around the above caveat, not being able to set during render, by making <code>myLog</code> invoke an async-IIFE, and waiting a tiny bit (i.e.: setting slightly after render):</p>
<pre><code class="js language-js">// now we're passing in the args directly so that they
// are tracked (all args are auto-tracked in all
// function / helper / component / modifier execution
// coming from the template)
function myLog(...args) {
  async function run() {
    await 0;
    // causes a change in a's data
    // and because we awaited, we don't infinite loop 
    setA(); 
    // prints a pre-setA, because a was passed in
    console.log(...args);
  }
 // return nothing, render nothing 
 // (we have no result to show the user)
}

&lt;template&gt;
  {{myLog (a) (b) (c)}}
&lt;/template&gt;</code></pre>
<p>This is nuanced, and is why I made this tiny abstraction a whole thing over here <a href="https://reactive.nullvoxpopuli.com/functions/sync.sync.html">https://reactive.nullvoxpopuli.com/functions/sync.sync.html</a><br />
it's 95% documentation, 5% code 😅 </p>
<hr />
<p>So coming back to:</p>
<blockquote>
  <p>"We deliberately don't have effects"</p>
</blockquote>
<p>Because of a couple current facts about our ecosystem:</p>
<ul>
<li>we want derived data to be preferred, because it is the most efficient way to have your render state settle</li>
<li>calling a function from a template can only happen after the template is rendered, so doing so causes a <em>second render</em> (I believe this is true in React as well) </li>
<li>there <em>is</em> a need to synchronize external state, and that has been part of the exploration of <em>Resources</em>, and <code>Sync</code><ul>
<li><a href="https://newdocs-rho.vercel.app/docs/universal/fundamentals/sync.html">Starbeam Docs on <code>Sync</code></a></li>
<li><a href="https://www.starbeamjs.com/guides/fundamentals/resources.html">Starbeam Docs on <code>Resource</code>s</a></li>
<li>Current ember implementation does not have <code>sync</code> capabilities: <a href="https://github.com/NullVoxPopuli/ember-resources/tree/main/docs">ember-resources</a> (due to limitations of the private APIs implementing reactivity (ember-resources is public-API only))</li>
<li><a href="https://tutorial.glimdown.com/2-reactivity/5-resources">Tutorial Chapters on Resources</a> </li></ul></li>
<li>we think that effects are <em>overused</em> and a huge footgun (for app devs), so by documenting a story more around synchronizing external state, we can continue to guide devs in to a pit of success.</li>
</ul>
<p>Note: Starbeam is where we're extracting our reactivity primitives, and are planning to swap to Starbeam entirely at some point once we work out some composition ergonomics for serial Resources (the coloring problem).</p>
<p>Hope this helps! <br />
If anything is unclear or if you have more questions, let me know!</p>]]></description><link>https://nullvoxpopuli.com/2024-06-07-effects-in-ember</link><guid isPermaLink="true">https://nullvoxpopuli.com/2024-06-07-effects-in-ember</guid><pubDate>Fri, 07 Jun 2024 05:00:00 GMT</pubDate></item><item><title><![CDATA[Where Do I Import X From]]></title><description><![CDATA[<h1 id="where-do-i-import-the-thing">Where do I import the thing?</h1>
<p>Ember has historically allowed access to everything in a global scope, like web-components. This proved confusing for developers, and Ember has moved to a more explicit, import-what-you-need approach, called "template-tag" -- more information on that <a href="https://guides.emberjs.com/release/components/template-tag-format/">here, in the official guides</a>.</p>
<p>A common thing I hear from folks is that they don't know where to import a component/modifier/helper from a particular addon, or maybe assume that because something isn't documented, they can't import the component/modifier/helper.</p>
<p>Good news:<br />
<em>if it works in loose mode (the globals way)</em>, <strong>the import is public API</strong>.</p>
<p>Here is how you find them:</p>
<ol>
<li><p><em>open <code>node_modules/${libraryName}</code></em></p>
<ul>
<li>if it doesn't exist, install <code>libraryName</code> / add to <em>your</em> <code>package.json</code> </li></ul></li>
<li><p>open the <code>package.json</code> of <code>libraryName</code></p>
<ul>
<li><p>does it have <code>exports</code>?</p>
<ul>
<li><p>This <em>could</em> look like this for types-providing projects:</p>
<pre><code class="js language-js">"exports": {
  ".": {
    "types": "./declarations/index.d.ts",
    "default": "./dist/index.js"
  },
  "./*": {
    "types": "./declarations/*.d.ts",
    "default": "./dist/*.js"
  },
  "./addon-main.js": "./addon-main.cjs"
},</code></pre></li>
<li><p>or it could look like this for non-types-providing projects </p>
<pre><code class="js language-js">"exports": {
  ".": "./dist/index.js",
  "./*": "./dist/*.js",
  "./addon-main.js": "./addon-main.cjs"
},</code></pre></li></ul>
<p>Exports say if you import x from <code>library-name/${sub path specifier}</code>, the <code>exports</code> map will match to a file on disk. So you can look through the dist directory if <code>./*</code> is specified and see what all you can import. See <a href="https://nodejs.org/api/packages.html#subpath-exports">the docs</a> for details.  <br />
For example, if you see a file in dist/components/foo.js (and given the above exports), you can import it at <code>library-name/components/foo</code>.  </p>
<hr />
<p>There is an intermediary step which <em>may</em> or may not ever be relevant, due to how strongly folks follow conventions in with the library blueprints, but if there is an <code>ember-addon.app-js</code> key in the package.json -- this is the mapping of what is dumped in to the globals resolver, and is what is exposed to you pre-gjs/gts/template-tag. This config will looks something like this:  </p>
<pre><code class="js language-js">"ember-addon": {
  "version": 2,
  "type": "addon",
  "main": "addon-main.cjs",
  "app-js": {
    "./components/foo.js": "./dist/some-other-folder/foo.js"
  }
},</code></pre>
<p>In this scenario, instead of importing from <code>libraryName/components/foo</code>, you'd import from <code>libraryName/some-other-folder/foo</code>.  </p>
<p>Once you find the file in <code>dist</code>, you're done.</p></li></ul>
<ol>
<li>If the package.json you're looking at does not have an <code>exports</code> config, <em>and</em> the library is an ember-addon (has <code>ember-addon</code> listed in <code>keywords</code>), this likely the older "v1 addon" format, which was convention-based, and didn't follow broader standards (as they didn't exist yet). <em>By convention</em>, you'll need to check the <code>app</code> folder for your components/modifiers/helpers, and see what those files define or re-export. You can then import what those files use.</li></ol>
<p>For example, if you find <code>app/components/foo.js</code> contains </p>
<pre><code class="js language-js">export { default } from 'libraryName/some-other-folder/foo'</code></pre>
<p>you can import from that same location. e.g.:</p>
<pre><code class="js language-js">import theDefaultExport from 'libraryName/some-other-folder/foo';</code></pre></li>
</ol>
<p>This information is also available on the <a href="https://github.com/ember-template-imports/ember-template-imports?tab=readme-ov-file#reference-import-external-helpers-modifiers-components">ember-template-imports</a> README.</p>
<h2 id="dont-know-which-library-to-start-with">Don't know which library to start with?</h2>
<p>you can find anything by searching in <code>node_modules</code>.</p>
<p>I like using <a href="https://github.com/ggreer/the_silver_searcher">the_silver_searcher</a>, but any search tool will work.</p>
<pre><code class="bash language-bash">❯ ag --unrestricted "&lt;ResponsiveImage" --file-search-regex '.js$'</code></pre>
<p>I use <code>--unrestricted</code> to search ignored files (<code>node_modules</code>), and <code>--file-search-regex</code> with <code>.js$</code>, because I want to exclude source-map files, <code>.js.map$</code> (I haven't learned how to read those).</p>
<p>So if I want to search for "ResponsiveImage" (a component that's used on this site), I don't get results in node_modules, but in my <code>dist</code> folder (my app's output) -- this means I need to try one of the other forms of which components can be referenced.</p>
<p>These are the possibilities for the direct name reference:</p>
<ul>
<li><p><code>&lt;PascalCase</code>, hoping that the component's JSDoc exists</p>
<pre><code class="bash language-bash">❯ ag --unrestricted "&lt;ResponsiveImage" --file-search-regex '.js$'</code></pre></li>
<li><p><code>class PascalCase</code> to find the file in JS </p>
<pre><code class="bash language-bash">❯ ag --unrestricted "class ResponsiveImage" --file-search-regex '.js$'</code></pre></li>
</ul>
<p>But we can also search via file path:</p>
<ul>
<li><p><code>kebab-case</code></p>
<pre><code class="bash language-bash">❯ ag --unrestricted --filename-pattern 'responsive-image'</code></pre>
<p>using <code>--filename-pattern</code> allows us to search for file paths, and not the contents of the file. We might not know what is in a particular file.</p>
<ul>
<li>or, if too many results, you can search with the extensions:</li>
<li><code>kebab-case.js</code></li>
<li><code>kebab-case.ts</code></li>
<li><code>kebab-case.gjs</code></li>
<li><code>kebab-case.gts</code></li>
<li><code>kebab-case.hbs</code></li>
<li><code>kebab-case/index.js</code></li>
<li><code>kebab-case/index.ts</code></li>
<li><code>kebab-case/index.gjs</code></li>
<li><code>kebab-case/index.gts</code></li>
<li><code>kebab-case/index.hbs</code></li></ul>
<p>All can be searched for all at once with this (which can still be far fewer results than with no extension):</p>
<pre><code class="bash language-bash">❯ ag --unrestricted --filename-pattern 'responsive-image(\/index)?\.(js|ts|hbs|gjs|gts)'</code></pre></li>
</ul>
<p>Feel like a lot of work to find which library something comes from? I think so, too.</p>
<p>This is why gjs/gts/template-tag is so nice, because you <em>just know</em> exactly where something is coming from.</p>
<p>To try out gjs / <code>&lt;template&gt;</code> in your browser, check out this <a href="https://tutorial.glimdown.com">interactive tutorial</a>.</p>]]></description><link>https://nullvoxpopuli.com/2024-07-18-where-do-i-import-x-from</link><guid isPermaLink="true">https://nullvoxpopuli.com/2024-07-18-where-do-i-import-x-from</guid><pubDate>Thu, 18 Jul 2024 15:14:31 GMT</pubDate></item><item><title><![CDATA[My pi.hole was periodically blocking linux]]></title><description><![CDATA[<p>Ubuntu Linux ships with the ability to reach any host on your local network via <code>$HOSTNAME.local</code>, where if the <code>hostname</code> for a machine on your network is <code>$HOSTNAME</code>, it may be reached at <code>$HOSTNAME.local</code> instead of its ip address. This is a very convinient feature since working with ip addresses are somewhat obnoxious. </p>
<p>In any case, while this is useful, I have manually set up some DNS entries on my <a href="https://pi-hole.net/">pi.hole</a> (which I use for network-wide ad-blocking for all devices (phones!)).</p>
<p>The pi.hole has a default DNS request rate limit of 1000 requests in 60 seconds. And Ubuntu was regularly surpassing this by doing "Reverse DNS" PTR requests to <code>in-addr.arpa</code>. Now, every time I looked at the pi.hole logs, these were cache-hits, but the fact of exceeding 1000 requests in 60 seconds remained. When this limit is exceeded, I was no longer able to operate my computer where DNS requests were required (such as during <code>git push</code>).</p>
<p>Through some light investigation, I found that this built-in behavior bundled with the default Ubuntu install is caused by a program called <code>avahi-daemon</code>. </p>
<p>So, to fix my issue, I just uninstalled <code>avahi-daemon</code></p>
<pre><code class="bash language-bash">sudo apt remove avahi-daemon</code></pre>
<p>And my problems were solved!</p>
<p><a href="https://superuser.com/a/316767">This StackOverflow/SuperUser post</a> is a good resource for details</p>]]></description><link>https://nullvoxpopuli.com/2024-11-17-ubuntu-too-many-reverse-dns-requests</link><guid isPermaLink="true">https://nullvoxpopuli.com/2024-11-17-ubuntu-too-many-reverse-dns-requests</guid><pubDate>Mon, 18 Nov 2024 03:05:16 GMT</pubDate></item><item><title><![CDATA[Design Systems in Ember]]></title><description><![CDATA[<p>Design systems help you prototype apps faster, and focus on shipping, rather than re-inventing the wheel.</p>
<p>CSS frameworks a great way to start a project, but long-lived projects need interactivity to truly feel like an application. </p>
<p>This post is a curation of libraries to help you get started with bootstrapping your app, or creating your own design system, so you don't need to go looking for things!<br />
(and I'll try to keep it up to date as new libraries are published).</p>
<blockquote>
  <p>NOTE: Some of what is in here is my opinion, and not necessarily representative of the opinions of others / groups I'm a member of. </p>
</blockquote>
<p>As of 2025, these are a good few maintained design systems, and here I've broken them up into some categories: <br />
The "Polaris" category can be used as a learning tool to see how folks should be writing ember <em>today</em>. <br />
There is "Pre-Polaris", which means that the design system is nearly Polaris, but missing one or two modern patterns to be considered Polaris.<br />
Lastly, there is the "Needs Work" category, which is to concisely say that the design systems has a number of migrations to do before I would recommend someone look at the code for learning how to write modern Ember.</p>
<style>
  /* blog theme uses the wrong default */
  p img {
    max-width: 100% !important;
  }
</style>
<h2 id="polaris">Polaris</h2>
<ul>
<li><p><a href="https://github.com/prysmex/ember-eui/tree/master">Elastic EUI</a> » <a href="https://ember-eui.vercel.app/">Docs</a> | <a href="https://www.npmjs.com/package/@ember-eui/core">npm</a></p>
<p><img src="/images/design-systems/ember-eui.png" alt="screenshot of EUI" /></p>
<pre><code class="bash language-bash">npm add @ember-eui/core </code></pre>
<p>This design system from <a href="https://www.prysmex.com/">Prysmex</a> is a fully modern, thorough, up to date design system.<br />
There are enough components to build whole products without creating any additional components.</p></li>
<li><p><a href="https://github.com/hokulea/hokulea">Hokulea</a> » <a href="https://hokulea.netlify.app/">Docs</a> | <a href="https://www.npmjs.com/package/@hokulea/ember">npm</a> </p>
<p><img src="/images/design-systems/hokulea.png" alt="screenshot of Hokulea" /></p>
<pre><code class="bash language-bash">npm add @hokulea/ember</code></pre>
<p>Hokulea is a fully modern <em>whimsical</em> design system from <a href="https://github.com/gossi">gossi</a> that demonstrates Storybook integration and workflows for documenting each component. This design system supports themeing, is research driven, and has enough components to build a real product without needing to write more components!</p></li>
<li><p><a href="https://github.com/josemarluedke/frontile">Frontile</a> » <a href="https://frontile.dev/">Docs</a> | <a href="https://www.npmjs.com/package/@frontile/buttons">npm</a> </p>
<p><img src="/images/design-systems/frontile.png" alt="screenshot of Frontile" /></p>
<pre><code class="bash language-bash">npm add @frontile/buttons @frontile/overlays # ... </code></pre>
<p>This design system from <a href="https://github.com/josemarluedke">josemarluedke</a> is a fully modern, thorough, up to date design system.<br />
There are enough components to build whole products without creating any additional components.</p></li>
<li><p><a href="https://github.com/IBM/carbon-components-ember">Carbon</a> » <a href="https://ibm.github.io/carbon-components-ember/">Docs</a> | <a href="https://www.npmjs.com/package/carbon-components-ember">npm</a> </p>
<p><img src="/images/design-systems/carbon.png" alt="screenshot of Carbon" /></p>
<pre><code class="bash language-bash">npm add carbon-components-ember</code></pre>
<p>This design system from <a href="https://www.ibm.com/">IBM</a> is one of the more modern codebases, though, in poking around their documentation, I found a few CSS bugs. <br />
They use TypeScript, gjs/gts, and the V2 Addon (native library) format.</p></li>
<li><p><a href="https://github.com/IgnaceMaes/shadcn-ember">shadcn-ember</a> » <a href="https://shadcn-ember.com/">Docs</a> | <a href="https://www.npmjs.com/package/shadcn-ember">npm</a></p>
<p><img src="/images/design-systems/shadcn-ember.png" alt="screenshot of shadcn-ember" /></p>
<pre><code class="bash language-bash">npx shadcn-ember@latest init</code></pre>
<p>This design system from <a href="https://github.com/IgnaceMaes">Ignace Maes</a> is an Ember port of <a href="https://ui.shadcn.com/">shadcn/ui</a>. It uses a CLI to copy beautifully designed, accessible components directly into your project for full customization. Built with TypeScript, gts, and Tailwind CSS v4.</p></li>
<li><p><a href="https://github.com/hashicorp/design-system">Helios</a> » <a href="https://helios.hashicorp.design/">Docs</a> | <a href="https://www.npmjs.com/package/@hashicorp/design-system-components">npm</a></p>
<p><img src="/images/design-systems/helios.png" alt="screenshot of Helios" /></p>
<pre><code class="bash language-bash">npm add @hashicorp/design-system-components</code></pre>
<p>Helios is a design system by <a href="https://www.hashicorp.com/">Hashicorp</a>, so it is well funded and maintained, as well as made with influence from accessibility experts.</p></li>
</ul>
<h2 id="needs-work">Needs Work</h2>
<p>These are also "V1 Addons" -- which are node programs that your app runs during your app's build, and the node programs happen to emit browser JavaScript.<br />
This is different from normal browser libraries (sometimes call "V2 Addons"), which are compiled when the library is published.</p>
<ul>
<li><p><a href="https://github.com/adfinis/ember-uikit">UI Kit</a> » <a href="https://docs.adfinis.com/ember-uikit/">Docs</a> | <a href="https://www.npmjs.com/package/ember-uikit">npm</a></p>
<p><img src="/images/design-systems/uikit.png" alt="screenshot of UI Kit" /></p>
<pre><code class="bash language-bash">npm add ember-uikit</code></pre>
<p>This design system looks polished, but it provides no Types, is not a V2 Addon, and doesn't use any gjs or gts components -- however, this does not mean it's not a solid choice for bootstrapping a new project! If you're writing javascript, and don't want to read this design-system's code, this is a <em>fine</em> option.</p></li>
<li><p><a href="https://www.ember-bootstrap.com/">Bootstrap</a> » <a href="https://www.ember-bootstrap.com/">Docs</a> | <a href="https://www.npmjs.com/package/ember-bootstrap">npm</a></p>
<p><img src="/images/design-systems/ember-bootstrap.png" alt="screenshot of Bootstrap" /></p>
<pre><code class="bash language-bash">npm add ember-bootstrap</code></pre>
<p>This library provides a good few interactive components where the <a href="https://getbootstrap.com/">Bootstrap</a> CSS Framework would not be sufficient for building applications.<br />
This library is not a V2 Addon, does not use Types, and doesn't use any gjs or gts components.</p></li>
<li><p><a href="https://github.com/adopted-ember-addons/ember-paper">Material Design</a> » <a href="https://ember-paper.netlify.app/">Docs</a> | <a href="https://www.npmjs.com/package/ember-paper">npm</a></p>
<p><img src="/images/design-systems/ember-paper.png" alt="screenshot of ember-paper" /></p>
<pre><code class="bash language-bash">npm add ember-paper</code></pre>
<p>Material design is starting to look a little dated these days, but maintenance of this design system is still on-going. It provides no types, is not a V2 Addon, and doesn't use any gjs or gts components.</p></li>
</ul>
<h2 id="want-to-build-your-own">Want to build your own?</h2>
<p>Ember authors great ergonomics for creating design systems!</p>
<p>And to help build design systems <em>even</em> faster, there a number of libraries that abstract away a lot of the menial things that you'd have to implement anyway on top of the platform (while deferring to the platform as often as possible ( as we all know: the best code is the code that isn't needed in the first place )) </p>
<p>UI Primitives:</p>
<ul>
<li><a href="https://emberobserver.com/addons/ember-sortable">ember-sortable</a> - primitives for sorting, drag &amp; drop</li>
<li><a href="https://ember-primitives.pages.dev/">ember-primitives</a> - Styleless, Accessibility-focused primitives that help you build faster UIs. Not just components, but light/dark mode management, popover management, etc.</li>
<li><a href="https://github.com/universal-ember/table">@universal-ember/table</a> - a plugin-based styleless way of building complex table UIs with sorting, column-reordering, etc</li>
<li><a href="https://github.com/CrowdStrike/ember-headless-form">ember-headless-form</a> - an Accessibility-focused way of taking the boilerplate out of highly-managed form experiences, including local errors, server errors, etc -- giving you the primitives, to expose powerful design-system forms with a great pit of success .</li>
<li><a href="https://emberobserver.com/addons/ember-focus-trap">ember-focus-trap</a> - element modifier for trapping focus within the element.</li>
</ul>
<p>Utility Primitives:</p>
<ul>
<li><a href="https://reactive.nullvoxpopuli.com/">reactiveweb</a> - low-level reactive utilities and reactive wrappers of common web APIs.</li>
<li><a href="https://emberobserver.com/addons/ember-keyboard">ember-keyboard</a> - easy key-combos.</li>
<li><a href="https://emberobserver.com/addons/ember-concurrency">ember-concurrency</a> - easy concurrent behaviors, prevent users from submitting forms multiple times.</li>
<li><a href="https://github.com/NullVoxPopuli/ember-statechart-component">ember-statechart-component</a> - Use XState statecharts as components, allowing easy drop-in diagram-based logic implementation.</li>
</ul>
<p>Testing and Documentation:</p>
<ul>
<li><a href="https://emberobserver.com/addons/@ember/test-waiters">@ember/test-waiters</a> - tie async-behaviors to the settled state system, so users have an easy time with tests (no wait-for, etc).</li>
<li><a href="https://emberobserver.com/addons/ember-a11y-testing">ember-a11y-testing</a> - Add AXE accessibility testing to your automated tests.</li>
<li><a href="https://github.com/universal-ember/test-support">@universal-ember/test-support</a> - Additional common test utilities to use on top of those provided from <a href="https://github.com/emberjs/ember-test-helpers"><code>@ember/test-helpers</code></a> or <a href="https://testing-library.com/docs/dom-testing-library/intro"><code>testing-library</code></a></li>
<li><a href="https://github.com/universal-ember/kolay">Kolay</a> - a runtime-rendered documentation framework allowing easy documentation of design systems, component libraries, all in a familiar markdown format. <ul>
<li>This is pre-alpha right now, so unless you want to help fix bugs, you may be more interested in:<ul>
<li><a href="https://docfy.dev/">Docfy</a> </li>
<li><a href="https://github.com/empress/field-guide">field-guide</a> </li></ul></li></ul></li>
</ul>
<p>Build Tools:</p>
<ul>
<li><a href="https://emberobserver.com/addons/@responsive-image/ember">@responsive-image/ember</a> automatically create responsive optimized images and fast page loads -- part of <a href="https://github.com/simonihmig/responsive-image">responsive-image</a></li>
<li><a href="https://github.com/svg-jar/plugin/blob/main/plugin/README.md">svg-jar</a> automatically <code>&lt;use&gt;</code> SVGs in rollup and vite</li>
</ul>
<p>Other:</p>
<ul>
<li><a href="https://github.com/RobbieTheWagner/ember-shepherd">Shepherd</a> - guided walkthroughts for introducing users to things</li>
</ul>]]></description><link>https://nullvoxpopuli.com/2025-01-16-design-systems-written-in-ember</link><guid isPermaLink="true">https://nullvoxpopuli.com/2025-01-16-design-systems-written-in-ember</guid><pubDate>Thu, 16 Jan 2025 17:18:50 GMT</pubDate></item><item><title><![CDATA[Ember has typesafe HTML]]></title><description><![CDATA[<p>Ember has had typesafe HTML for a while now. Details on getting it set up <a href="https://github.com/typed-ember/glint/blob/main/GLINT_V2.md">are here on the Glint repo</a>. </p>
<p>tl;dr: install the pre-release version of the Glint extension and enable <code>@builtin typescript</code>.</p>
<p>We're using the pre-release version of Glint here because it provides a far superior editing than the currently released stable version of Glint. Glint V2 is powered by <a href="https://volarjs.dev/">Volar</a> and lines up nicely with the <a href="https://emberjs.com/editions/polaris/">Polaris</a> efforts to align with broader ecosystem tooling in a way that unblocks all experimentation while also still supporting older code so that large codebase have a path to the modern tooling and DX.</p>
<p>For neovim, I need to PR to the built in configs to make switching to the glint ts-plugin automatic, but here is what it looks like!</p>
<p><img src="/images/typesafe-html/href.png" alt="image of href completion" /><br />
<img src="/images/typesafe-html/href-error.png" alt="image of href type error" /><br />
<img src="/images/typesafe-html/aria.png" alt="image of aria atttribute completion" /></p>]]></description><link>https://nullvoxpopuli.com/2025-04-05-ember-has-typesafe-html</link><guid isPermaLink="true">https://nullvoxpopuli.com/2025-04-05-ember-has-typesafe-html</guid><pubDate>Sat, 05 Apr 2025 17:18:50 GMT</pubDate></item><item><title><![CDATA[How to get started with Ember]]></title><description><![CDATA[<h1 id="how-to-get-started-with-ember">How to get started with Ember</h1>
<p>For questions / concerns / updates, feel free to ask in:</p>
<ul>
<li>The <a href="https://discord.gg/emberjs">Official Ember Discord</a></li>
<li>My <a href="http://discord.gg/cTvtmJhFNY">Personal Discord</a></li>
</ul>
<p><em>While you may use ember for your frontend application, most of the code you will encounter is non-ember aka “just javascript”. Learning ember will allow you to quickly get to shipping or debugging features for non-company-specific problems.</em></p>
<p><em>The goal is for the framework to stay out of your way</em>. If you feel any resistance to doing your work, <strong>regardless of the cause</strong>, please let me know.</p>
<h2 id="where-to-get-started">Where to get started</h2>
<ul>
<li>Already know another framework?<ul>
<li>Compare component-patterns between Ember Polaris and other frameworks <ul>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-react">React</a>, <a href="https://component-party.dev/compare/emberPolaris-vs-solid">Solid</a>, </li>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-vue3">Vue 3</a>, <a href="https://component-party.dev/compare/emberPolaris-vs-vue2">Vue 2</a></li>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-svelte5">Svelte 5</a>, <a href="https://component-party.dev/compare/emberPolaris-vs-svelte4">Svelte 4</a></li>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-lit">Lit</a></li>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-angularRenaissance">Angular Renaissance</a>, <a href="https://component-party.dev/compare/emberPolaris-vs-angular">Angular</a></li>
<li><a href="https://component-party.dev/compare/emberPolaris-vs-emberOctane">Ember Octane</a></li></ul></li></ul></li>
<li>Tutorials<ul>
<li>how-to and <a href="https://tutorial.glimdown.com/">interactive guide</a></li>
<li>full and <a href="https://guides.emberjs.com/release/tutorial/part-1/">official tutorial and walkthrough</a>, which guides you through building a real application </li></ul></li>
<li>AI Chat<ul>
<li>There is a <a href="https://chatgpt.com/g/g-NlX2z2g6H-ember-assistant">custom GPT in the ChatGPT marketplace</a>     trained on ember knowledge - it’s able to convert between frameworks, so if you don’t like tutorials or prefer to learn by trial and error, you may want to start here.<br />
(It’s important to use this over general trained AI, this custom GPT has been told to favor newer patterns instead of older patterns)</li></ul></li>
<li>Guided documentation:<ul>
<li>Guides on many topics for developing apps on <a href="https://guides.emberjs.com/release/components/">the official website</a></li></ul></li>
</ul>
<p>If you want to take a step back and learn things how the framework has organized, you may want to start on this page: <a href="https://emberjs.com/learn/">The official Learn Ember</a> page.</p>
<h2 id="once-you-have-some-familiarity">Once you have some familiarity</h2>
<ul>
<li><p>Reference documentation</p>
<ul>
<li><a href="https://api.emberjs.com/ember/release">Runtime Framework APIs</a></li>
<li><a href="https://api.emberjs.com/ember-data/release/modules/ember-data-overview?show=inherited">Data / warp-drive APIs</a><ul>
<li><a href="https://github.com/emberjs/data/tree/main/guides">Newer concepts</a> </li></ul></li></ul></li>
<li><p>Messin’ around</p>
<ul>
<li><a href="https://limber.glimdown.com/edit?c=JYWwDg9gTgLgBAYQuCA7Apq%2BAzKy4DkAAgOYA2oI6UA9AMbKQZYEDcAUKJLHAN5wwoAQzoBrdABM4AXzi58xcpWo1BI0cFQk2nFD35oZcvCEJF0IAEYqQECcGzBqO9ugAe3eBPTYhAVzJ4OjIhAGdQuAARCwg4dxhMCQikFGZ4XnY4OCI1MUk4AH0GPyw4AF44AAYOTLgSdCCIEpgACgBKPlqssgbjZABlGGghevK4MCEoUPQASSwWsgg6ITJB4fqAOnqYGYSQFoJiljaOgH5Tqo4srKgGvyhUAQALYFCNoqbSgB8vvpA14SjH6XWrSWrTRrNFoANxWfnQHQy1zgi2WqyGgPQGwhuwsByOMAIABo4LCyPC4Ocqm0rtcYC83h9mmMyfDaWDapo6LcqKUKu1ygA%2BZ6vDYEuAAagqAEYalkADx7MAhBKCrpweVgQUATSacCeQmh6DgwWAeSk9ONlj8MCGj14vHpooJ0lkMFA6De8poWvY6vl1tthhgAE8wOgygAiQN2yN8XiGSOmsRxp1vLk8zAwV2ChAUMTemNoNUK1QWZVCVXsMFAA&format=gjs">Online REPL</a> - fast way to “just try stuff”</li>
<li>Stackblitz - a little slower due to running a whole dev environment in your browser, but most like the local dev experience for default ember apps.<ul>
<li><a href="https://stackblitz.com/fork/github/ember-cli/editor-output/tree/stackblitz-app-output?title=Ember%20Starter">Ember (JavaScript)</a></li>
<li><a href="https://stackblitz.com/fork/github/ember-cli/editor-output/tree/stackblitz-app-output-typescript?title=Ember%20TypeScript%20Starter">Ember (TypeScript)</a></li></ul></li></ul></li>
</ul>
<h2 id="vocab">Vocab</h2>
<ul>
<li><a href="https://vite.dev/"><em>vite</em></a> - the build tool we use — which has many contributors and is leading the JavaScript ecosystem in local build performance</li>
<li><a href="https://github.com/embroider-build/embroider/"><em>embroider</em></a> - the set of tools we use for adapting pre-spec JS to spec-compliant JavaScript  <br />
This is what enables our &gt; 10 year old, multi-million line project to run in vite without a major migration / rewrite, enabling parallel shipping and maintenance.</li>
<li><em>tracked properties</em> - what ember calls its wrapper around Signals.  </li>
<li><em>addon</em> - alias for “library” </li>
<li><a href="https://webpack.js.org/"><em>webpack</em></a> - older and slower build tool that we’re migrating away from (or will already have migrated away from by the time you read this)</li>
<li><em>broccoli</em> - older and slower build tool that we’re migrating away from (or will already have migrated away from by the time you read this)</li>
</ul>]]></description><link>https://nullvoxpopuli.com/2025-04-08-how-to-get-started-with-ember</link><guid isPermaLink="true">https://nullvoxpopuli.com/2025-04-08-how-to-get-started-with-ember</guid><pubDate>Tue, 08 Apr 2025 12:22:45 GMT</pubDate></item><item><title><![CDATA[How to be Paranoid]]></title><description><![CDATA[<p>How do you <em>trust</em> that your machine is safe?</p>
<p>If we don't have exact <a href="https://www.crowdstrike.com/en-us/blog/crowdstrike-falcon-prevents-npm-package-supply-chain-attacks/">Indicators of Compromise</a>, what do we need to check for?</p>
<p>Initially, when I found out about  <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-54313">CVE-2025-54313</a> / <a href="https://github.com/advisories/GHSA-f29h-pxvx-f335">GHSA-f29h-pxvx-f335</a>, or <em>any</em> vulnerability, I have to decide if there is, beyond a shadow of a doubt, sufficient evidence to do nothing, else, I have to do a bunch of key-rolling -- which takes about 15 minutes on my personal hardware, and an hour and a half on work hardware (due to MacOS being way slower than linux).</p>
<p>In this case, the day prior, I was doing a lot of open source development for a library that my employer makes heavy use of, and noticed that my pull request was conflicting with <code>main</code> frequently due to <a href="https://github.com/renovatebot/renovate">renovate</a> running <em>very eagerly</em> on dependency updates. I needed the repo's CI to run all the scenarios for me, because enumerating all the tested / supported scenarios without knowing where errors exist is time consuming and cumbersome -- and GitHub does not run CI for pull request if there is a conflict. So I was regularly rebasing.</p>
<p>Normally this isn't a big deal, but I just <em>didn't know</em> -- which is not an acceptable state to be in to "decide to do nothing". </p>
<p>At the time I decided I had to deal with the possibility of my two computers being infected with the malware, I didn't have sufficient knowledge for a targeted sweep / clean of my systems. All I knew was that the malware would steal credentials and upload them somewhere.</p>
<p>So I had to do the heavy-handed investigative process:</p>
<ul>
<li><p>Disable WiFi / all internet connections on both devices</p></li>
<li><p>On my phone, review audit logs from GitHub.</p>
<ul>
<li>Discover than NPM does not have an audit log.</li>
<li>Discover that GitHub doesn't have an auditlog of your SSH (git cli) activity…</li>
<li>Delete SSH Keys on GitHub</li></ul></li>
<li><p>Write a script <a href="https://github.com/NullVoxPopuli/dotfiles/commit/051eb2144a837144ba1e7357becb2f4fb0024df8">now located here</a> to help scan for suspicious copies of the libraries reported in the above-linked reports.</p>
<ul>
<li>Through this process I realized that if I were trying to defend against myself, I would not win. Not knowing enough about the attack, I had to assume the worst:<ul>
<li>I can't sufficiently trust:<ul>
<li>checking for libraries installed on my desk under the name of the published package -- if I were trying to do an attack, I would have the code copy itself elsewhere </li>
<li>checking the library's version -- I would try to publish a malicious package with the version of a trusted copy of the package</li>
<li>checking for a <code>package.json</code> with the name of the affected packages -- I would change the name if I were trying to evade detection</li></ul></li>
<li>So my script scans all directories <code>node</code> has access to -- which for all intents and purposes include every folder that doesn't require <code>sudo</code> to access (as I never install dev tools with <code>sudo</code>)</li></ul></li></ul></li>
<li><p>The script didn't tell me I was compromised, but given that I kept thinking of ways to circumvent myself, I had to go further. I couldn't trust automated detection of an attack I didn't know the details of.</p></li>
<li><p>I decided to delete all <code>node_modules</code> on my machines</p>
<pre><code class="bash language-bash">find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +</code></pre>
<p>This took about 7 minutes for my hundreds of GB of JS projects an their dependencies.<br />
On the work MacOS it took about 45 minutes.</p></li>
<li><p>I also deleted things that I knew could have <code>node_modules</code>, but potentially not have them kept in a <code>node_modules</code> directory</p>
<ul>
<li>Uninstall and Delete caches of: Discord, Slack, Cursor, Windsurf, etc (All Electron-based applications)</li>
<li>Delete global cache directories: <code>rm -rf ~/.pnpm-store</code>, or <code>pnpm store prune</code>, for example.</li></ul></li>
<li><p>I suspected that if me as an attacker in the try-hardest mode I could think of potentially wouldn't even use node</p></li>
<li><p>Rebooting the computer would ensure that anything that began running would be stopped (be that a node process or otherwise)</p></li>
<li><p>However, if there is malicious code disguising itself, it's potentially possible it started up again at login</p>
<ul>
<li>This is where code-signing comes in to play -- applications that are not signed by a central authority do not have permission to install themselves and start up.<br />
(But short of having a machine with signing, you'd then need to inspect your network activity… which is involved)</li></ul></li>
</ul>
<p>When I started the computers back up again, I was ready to investigate the signatures of all the running binaries -- but then I found out the attack was only for windows machines (after confirming with 10+ well known security sources) -- and… I just stopped -- I didn't need to do all this work.</p>
<p><em>But I'm glad I did</em>. I solved my paranoia.</p>
<p>I also didn't re-install Cursor and Windsurf, which is a win.</p>]]></description><link>https://nullvoxpopuli.com/2025-07-24-how-to-be-paranoid</link><guid isPermaLink="true">https://nullvoxpopuli.com/2025-07-24-how-to-be-paranoid</guid><pubDate>Thu, 24 Jul 2025 18:39:49 GMT</pubDate></item><item><title><![CDATA[Avoiding Lifecycle in Components]]></title><description><![CDATA[<h1 id="avoiding-lifecycle-in-components">Avoiding Lifecycle in Components</h1>
<p>This is mostly going to be about <code>did-insert</code>, <code>did-update</code>, etc, aka, the<br />
<a href="https://github.com/emberjs/ember-render-modifiers"><code>@ember/render-modifiers</code></a>.</p>
<p><em>If you know of a pattern that you use the render-modifiers for and it feels awkward and is
not covered here, let me know</em></p>
<p>I'm writing about this, because I don't think there has been any guidance published<br />
on what to do. A long time ago<br />
(<a href="https://github.com/emberjs/ember-render-modifiers/commit/c836f83901a9068e6e3897f54cf4b7b9aa69ede5#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5">10 months ago</a>),<br />
a warning was added to the top of the <code>@ember/render-modifiers</code> README explaining:</p>
<blockquote>
  <p>The modifiers provided in this package are ideal for quickly migrating away from classic Ember components to Glimmer components, because they largely allow you to use the same lifecycle hook methods you've already written while attaching them to these modifiers. For example, a didInsertElement hook could be called by {{did-insert this.didInsertElement}} to ease your migration process.</p>
</blockquote>
<blockquote>
  <p>However, we strongly encourage you to take this opportunity to rethink your functionality rather than use these modifiers as a crutch. In many cases, classic lifecycle hooks like didInsertElement can be rewritten as custom modifiers that internalize functionality manipulating or generating state from a DOM element. Other times, you may find that a modifier is not the right fit for that logic at all, in which case it's worth revisiting the design to find a better pattern.</p>
</blockquote>
<blockquote>
  <p>Either way, we recommend using these modifiers with caution. They are very useful for quickly bridging the gap between classic components and Glimmer components, but they are still generally an anti-pattern. We recommend considering a custom modifier in most use-cases where you might want to reach for this package.</p>
</blockquote>
<p>So yeah, there is kind of a lot to unpack there. <em>Especially</em> since we haven't really had an<br />
alternative to the data-side / non-dom-related-side of things for a long while. Throughout this post, there will be<br />
bulleted lists showing the benefits of each alternative -- because I usually skim long posts with code,<br />
and I'm sure others do as well -- bulleted lists stad out ;)</p>
<h2 id="using-a-custom-modifier">Using a custom modifier</h2>
<p>A custom modifier is good solution for when your behavior is tied to a particular DOM node<br />
or DOM tree. It encapsulates the lifecycle of rendering in to one co-located space so that<br />
it's easy to understand what code is responsible for what.</p>
<p>❌ Bad - setup and teardown are not grouped together</p>
<pre><code class="hbs language-hbs">&lt;div
  {{did-insert this.setupResize}}
  {{did-update this.updateResize}}
  {{will-destroy this.teardownResize}}&gt;&lt;/div&gt;</code></pre>
<p>In addition, this pattern also encourages components that have multiple responsibilities.<br />
The component that contains this setup/update/down for resize may also have other responsibilities,<br />
like maybe it's also managing a form, or a table, or something.</p>
<p>✔️  Good - behavior is grouped</p>
<pre><code class="hbs language-hbs">&lt;div {{resizable}}&gt;&lt;/div&gt;</code></pre>
<p>This pattern collects all the related behavior in to a semantically standalone thing, a <em>custom modifier</em>.<br />
There are built in modifiers, and you may be familiar with them, as they're the only two modifiers<br />
in ember right now (3.27 latest at the time of writing): <code>{{on}}</code> and <code>{{action}}</code>.</p>
<p>How would the JavaScript side of this look? In the first example, the code in a component may look something<br />
like this:</p>
<pre><code class="js language-js">// app/components/my-component.js
export default class MyComponent extends Component {
  // ... component stuff ...

  @action setupResize() { /* ... */ }

  @action updateResize() {/* ... */}

  @action teardownResize() { /* ... */ }

  // ... component stuff ...
}</code></pre>
<p>which looks fine in isolation, and if this were <em>all</em> a component did, it would not be cause for too much alarm.<br />
But as components grow, and folks add features to existing components, those 3 functions get crowded. Extracting<br />
them to a custom modifier is a great way to focus on a component's core responsibilities.<br />
That extracted JavaScript may look like this:</p>
<pre><code class="js language-js">// app/modifiers/resizable.js
import Modifier from 'ember-modifier';

export default class Resizable extends Modifier {
  didInstall() { /* original setup code */ }
  didUpdateArguments() { /* original update code */ }
  willDestroy() { /* original teardown code */ }
}</code></pre>
<p>Benefits of the custom modifier</p>
<ul>
<li>all element tied to a behavior is encapsulated in a single class</li>
<li>easier to keep track of cleanup</li>
<li>can be render-tested outside of the component where it is used</li>
<li>shareable among other elements</li>
<li>reads better when parsing the template with your eyes</li>
</ul>
<h2 id="using-a-local-modifier">Using a local modifier</h2>
<p>Sometimes you are not sure if your modifier needs to be globally accessible or you want to keep it to yourself<br />
while you work out the kinks before shareing it with your team. Since Ember 3.25, you can assign modifiers,<br />
helpers, and components to class properties on components to reference locally in your component.</p>
<p>For example, say you decided that the above <code>resizable</code> modifier wasn't ready to be shared with folks,<br />
you could rearrange your files like so:</p>
<pre><code class="diff language-diff">-app/components/my-component.hbs
-app/components/my-component.js
+app/components/my-component/index.hbs
+app/components/my-component/index.js
-app/modifiers/resizable.js
+app/components/my-component/resizable.js</code></pre>
<p>Also note: we moved <code>my-component.{js,hbs}</code> to <code>my-component/index.{js,hbs}</code>, not because we had to, but because<br />
it (to me) feels nicer to have a folder contain our "private-to-the-component" stuff.</p>
<p>In the <code>my-component/index.js</code>, you'll need to import and assign the modifier</p>
<pre><code class="js language-js">// app/components/my-component/index.js

import Resizable from './resizable';

export default class MyComponent extends Component {
  resizable = Resizable;

  // ... component stuff ...
}</code></pre>
<pre><code class="hbs language-hbs">&lt;div {{this.resizable}}&gt;&lt;/div&gt;</code></pre>
<p>It'll work the exact same is the previously globally available version.</p>
<p>Benefits of a local modifier</p>
<ul>
<li>most of the benefits of a custom modifier</li>
<li>additionally, modifiers that are "specific to a thing", can be kept private~ish (as private as JS allows anyway)</li>
<li>allows for easier prototyping without interfering with the global pool of modifiers</li>
</ul>
<h2 id="more-information-on-modifiers">More information on modifiers</h2>
<p>Modifier abstractions aren't yet adopted into the framework, but you can learn more here</p>
<ul>
<li><a href="https://github.com/ember-modifier/ember-modifier">ember-modifier</a><br />
Lots of information in here about the philosophy and thought process about when to use a modifier.</li>
<li><a href="https://github.com/emberjs/rfcs/pull/757">RFC #757: Default Modifier Manager</a><br />
For using plain vanilla JavaScript functions as modifiers when used in the modifier position in template syntax.</li>
<li><a href="https://github.com/emberjs/rfcs/pull/415">RFC #416: Render Element Modifiers</a><br />
Original RFC detailing a transition path from old ember component lifecycle hooks</li>
</ul>
<h2 id="fetching-data">Fetching data</h2>
<p>❌ Bad - Modifier has nothing to do with the element.</p>
<pre><code class="hbs language-hbs">&lt;div {{did-insert this.fetchData}} {{did-update this.fetchData @someArg}}&gt;&lt;/div&gt;</code></pre>
<p>This also requires that your component have a template -- provider components, for example, do not need a template.<br />
Additionally, this means that data is eagerly fetched, so even if your component doesn't need the data right away,<br />
that data-fetching slows down your time-to-settled.</p>
<p>✔️  Good - Data is reactively and lazyily fetched as it is needed via a Resource.</p>
<pre><code class="js language-js">import { trackedFunction } from 'ember-resources/util/function';
export default class MyComponent extends Component {
  data = trackedFunction(this, async () =&gt; {
    let response = await fetch(`url/${this.args.someArg}`);
    let json = await response.json();

    return json;
  });
}</code></pre>
<pre><code class="hbs language-hbs">{{!-- when ready for use --}}
{{this.data.value}} will be the fetch's json</code></pre>
<p>With this approach, no modifier is used and no element is needed. <code>data</code> will call your function when the <code>.value</code><br />
property is accessed, and it will "eventually" resolve to the returned <code>json</code> in the inner function.</p>
<p>There are a number of utilities in <code>ember-resources</code> for dealing with "Reactive async~ish" data a little nicer.<br />
See the <a href="https://github.com/nullVoxPopuli/ember-resources">README</a> over there for more details.</p>
<p>Benefits of using a Resource:</p>
<ul>
<li>lazy, only runs when accessed</li>
<li>reactive, changes to tracked data will re-invoke the resource</li>
<li>everything is encapsulated, no need to worry about template <-> javascript communication</li>
<li>easily unit testable</li>
<li>can be used in vanilla JavaScript classes</li>
</ul>
<p>✔️  Good - Data is lazily fetched after a user action </p>
<p>This is probably the best case scenario for data loading, even though it is <em>non-reactive</em>.</p>
<pre><code class="js language-js">export default class MyComponent extends Component {
  @action
  async loadData() {
    let response = await fetch(`url/${this.args.someArg}`);
    let json = await response.json();

    return json;
  }

  @tracked data;

  @action 
  async handleClick() {
    this.data = await loadData();
    /* ... do something with data */
  }
}</code></pre>
<pre><code class="hbs language-hbs">{{!-- when ready for use --}}
{{this.data}} will be the fetch's json</code></pre>
<p>This is the most optimized that you can make a network request, and aligns with ember-data's new request manager usage recommendations.  Most notably, however, if <code>this.args.someArg</code> changes, the data will <em>not</em> update, because data fetching was triggered by the user, not the autotracking system.</p>
<h2 id="handling-destruction">Handling destruction</h2>
<p>For this example, assume we have a  class constructor that we've bound some events to the window.<br />
Maybe <code>beforeunload</code> (to protect against accidental refreshes while editing a form).</p>
<p>❌ Bad - Modifier has nothing to do with the element.</p>
<pre><code class="hbs language-hbs">&lt;div {{will-destroy this.removeWindowListeners}}&gt;&lt;/div&gt;</code></pre>
<p>This sometimes has caused folks to add an invisible element <em>just so that they can use the modifier hook</em>.<br />
We should not add more DOM than we absolutely need, and behavior setup in JS should be torn down in JS.</p>
<p>✔️  Good -- no modifiers needed</p>
<pre><code class="hbs language-hbs">{{!-- No element --}}</code></pre>
<pre><code class="js language-js">import { registerDestructor } from '@ember/destroyable';

class MyComponent extends Component {
  constructor(owner, args) {
    super(owner, args);

    this.setupWindowListener();
    this.setupScrollListener();
  }

  @action setupWindowListener() {
    /* setup */
    window.addEventListener('beforeunload', this.handleUnload)

    registerDestructor(this, () =&gt; {
      /* teardown */
      window.removeEventListener('beforeunload', this.handleUnload)
    });
  }

  @action setupScrollListener() {
    /* setup */

    registerDestructor(this, () =&gt; { /* teardown */ });
  }
}</code></pre>
<p>For <code>@glimmer/component</code>, there is also the <a href="https://api.emberjs.com/ember/release/modules/@glimmer%2Fcomponent#willdestroy">willDestroy</a> hook,<br />
but I'd argue that co-locating setup and teardown is better long-term, because in each setup-teardown pair,<br />
you know exactly what conditions are needed to safely teardown. Keeping that grouped together can make maintenance<br />
easier if or when you need several teardown steps for various things (maybe you setup a few listeners, a Mutation<br />
Observer, other stuff).</p>
<p>Benefits of <code>@ember/destroyable</code></p>
<ul>
<li>co-locates setup+teardown</li>
<li>can be used anywhere, not just in ember constructs</li>
<li>eliminates the need for "willDestroy" hooks provided by a framework</li>
<li>can easily share combined sets of setup+teardown functions</li>
</ul>
<p>Docs on <a href="https://api.emberjs.com/ember/release/modules/@ember%2Fdestroyable"><code>@ember/destroyable</code></a></p>
<p>Related, if you find the registerDestructor setup/teardown dance a bit tiring, there is a utility library,<br />
<a href="https://github.com/NullVoxPopuli/ember-lifecycle-utils">ember-lifecycle-utils</a> which provides a utility to<br />
allow you to eventually create concise apis like:</p>
<pre><code class="js language-js">class Hello {
  constructor() {
    useWindowEvent(this, 'click', this.handleClick);
    useWindowEvent(this, 'mouseenter', this.handleClick);
  }
}</code></pre>
<h2 id="more-info-on-resources">More info on Resources</h2>
<ul>
<li><p><a href="https://www.pzuraq.com/introducing-use/">Introducing <a href="https://github.com/use">@use</a></a> by <a href="https://www.pzuraq.com/">pzuraq</a></p></li>
<li><p><a href="https://github.com/pzuraq/ember-could-get-used-to-this">ember-could-get-used-to-this</a></p></li>
<li><p><a href="https://github.com/NullVoxPopuli/ember-resources">ember-resources</a></p>
<ul>
<li>note that ember-resources is inspired by pzuraq's work, and solves some of the common challenges<br />
that the ember-could-get-used-to-this' implementation faces:</li>
<li>Typescript support without wrapper no-op methods<ul>
<li>No need for decorator (typescript can't augment types with decorators)</li></ul></li>
<li>Custom Resources no longer need a magic 'value' property</li>
<li>Built in support for (async) functions and concurrency tasks</li></ul>
<p>Much thanks to pzuraq, because without ember-could-get-used-to-this, ember-resources would not have existed.</p></li>
<li><p><a href="https://github.com/emberjs/rfcs/pull/567">RFC 567: <code>@use</code> and Resources</a></p>
<ul>
<li>Discussion that lead to a bunch of smaller RFCs and the implementation of ember-could-get-used-to-this</li></ul></li>
</ul>
<h2 id="why-even-change-how-i-write-code-at-all">Why even change how I write code at all?</h2>
<p>You don't have to, that's up to you. One of the primary reasons <code>@ember/render-modifiers</code> was made as an<br />
addon was so that it <em>didn't</em> have to be part of the framework, and that individual projects and teams<br />
could decide if it was the right fit for them. It's a very small package, so if it's only used in a few places,<br />
bundle size isn't that much of a concern for the average app.</p>]]></description><link>https://nullvoxpopuli.com/avoiding-lifecycle</link><guid isPermaLink="true">https://nullvoxpopuli.com/avoiding-lifecycle</guid><pubDate>Fri, 30 Jul 2021 12:22:45 GMT</pubDate></item></channel></rss>