<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ste Griffiths</title>
    <description>The latest articles on DEV Community by Ste Griffiths (@stegriff).</description>
    <link>https://dev.to/stegriff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F8586%2Fca46cc07-f991-4632-84a0-f8b99eb28f31.jpg</url>
      <title>DEV Community: Ste Griffiths</title>
      <link>https://dev.to/stegriff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stegriff"/>
    <language>en</language>
    <item>
      <title>F1 Wow Dashboard - on Uno</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Fri, 05 Dec 2025 20:32:51 +0000</pubDate>
      <link>https://dev.to/stegriff/f1-wow-dashboard-on-uno-4090</link>
      <guid>https://dev.to/stegriff/f1-wow-dashboard-on-uno-4090</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/unoplatform"&gt;AI Challenge for Cross-Platform Apps&lt;/a&gt; - WOW Factor&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I love Formula 1, and all kinds of mission control dashboard and visualisations. So I built an &lt;strong&gt;F1 track dashboard with a rotating 3D car!&lt;/strong&gt; 🏎️&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rotating 3D car wireframe&lt;/li&gt;
&lt;li&gt;Animated track list&lt;/li&gt;
&lt;li&gt;Data bindings of track name, total laps&lt;/li&gt;
&lt;li&gt;Lap counter&lt;/li&gt;
&lt;li&gt;Name customisation&lt;/li&gt;
&lt;li&gt;Custom Splash Screen&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's a video of my app running on Windows:&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;

&lt;p&gt;Or check out the web assembly version of &lt;a href="https://gentle-sea-01556e203.3.azurestaticapps.net/" rel="noopener noreferrer"&gt;F1 Wow&lt;/a&gt; in your browser. &lt;em&gt;(I had to disable 3D model animation on WASM for performance reasons)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Platform Magic
&lt;/h2&gt;

&lt;p&gt;I was mainly targeting Windows, but I was pleased to see how it worked in WASM with no extra work! My 3D renderer is CPU-intensive, so I decided to disable this in the web version for better performance.&lt;/p&gt;

&lt;p&gt;I'd love to test this out on Android (but I don't have one) or iOS (but I don't have a Mac to build on! 😅)&lt;/p&gt;

&lt;p&gt;You can find the source for my &lt;a href="https://github.com/SteGriff/UnoWowApp" rel="noopener noreferrer"&gt;UnoWowApp on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows/Desktop
&lt;/h3&gt;

&lt;p&gt;It runs on Windows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5d3bjyk6cpgaf55tns8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5d3bjyk6cpgaf55tns8.png" alt="F1 Wow running on Windows Desktop with rotating car model, dynamically sized list, and track selection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Web
&lt;/h3&gt;

&lt;p&gt;And you can try it &lt;a href="https://gentle-sea-01556e203.3.azurestaticapps.net/" rel="noopener noreferrer"&gt;on the web&lt;/a&gt;:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Interactive Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3D Car Wireframe
&lt;/h3&gt;

&lt;p&gt;Originally I wanted a fully textured HD car render, but I didn't know how to do this, and out of the ways I researched, SkiaSharp was the best cross-platform option. To make a hardware accelerated 3D render, I think I would have needed platform dependent code, so I didn't go that way.&lt;/p&gt;

&lt;p&gt;Claude Sonnet 4.5 in GHCP helped me write the &lt;code&gt;Simple3DRenderer&lt;/code&gt; and I applied some tweaks after reading the code and learning about how it worked. This was a fun part of the experience as I've never worked with 3D rendering before!&lt;/p&gt;

&lt;h3&gt;
  
  
  Fancy Animated List
&lt;/h3&gt;

&lt;p&gt;I built a radial effect on the circuit list, so when you move the mouse, the tracks change size depending on how close the cursor is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcy5cj3eju473xc8i75d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcy5cj3eju473xc8i75d6.png" alt="Track list items dynamically resizing on mouse hover"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Name Entry
&lt;/h3&gt;

&lt;p&gt;This is kind of a basic one, but I used a text entry field to allow you to type in the driver name to get a custom greeting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lap Counter
&lt;/h3&gt;

&lt;p&gt;Press the Plus button in the top right to count laps, and when it passes the max laps for your chosen track, it loops back to one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wow Factor
&lt;/h2&gt;

&lt;p&gt;I think the combination of the background, 3D render, track list, and F1 styling creates a big overall impression. I like looking at this dashboard and I hope you do too.&lt;/p&gt;

&lt;p&gt;I completed the finesse of the app with a custom splash screen and icon to go with the F1 theme.&lt;/p&gt;

&lt;p&gt;It's been 10 years since I coded for Windows desktop, and my first time writing cross-platform. This was a fun experience! Thanks for checking out my entry, and let me know what you think 😁🏎️&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits and Inspiration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inspiration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.behance.net/gallery/236294379/Experimenting-F1-Race-Dashboard-(In-Process-Project)" rel="noopener noreferrer"&gt;Mohsin Ali on Behance&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.behance.net/gallery/98562431/Dashboard-F1-Insights" rel="noopener noreferrer"&gt;Eneas Marín on Behance&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://static.vecteezy.com/system/resources/thumbnails/007/128/045/original/abstract-digital-particle-wave-and-light-abstract-abstract-particle-wave-animation-blue-glowing-waves-in-science-fiction-style-moving-forward-abstract-technology-big-data-background-concept-free-video.jpg" rel="noopener noreferrer"&gt;Particle wave background&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://sketchfab.com/3d-models/low-poly-f1-car-0aa61226d2f84450a17a05ab23f6c969" rel="noopener noreferrer"&gt;"Low Poly-F1" by salasilma13 is licensed under Creative Commons Attribution&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://gltf.report/" rel="noopener noreferrer"&gt;GLTF.Report - Model Optimiser&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.svgrepo.com/svg/480771/race-car-illustration-4" rel="noopener noreferrer"&gt;Race Car SVG&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>unoplatformchallenge</category>
      <category>dotnet</category>
      <category>crossplatform</category>
    </item>
    <item>
      <title>Converting an ancient Angular 11 app to Vue 3 with GitHub CoPilot</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Wed, 22 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/converting-an-ancient-angular-11-app-to-vue-3-with-github-copilot-2c39</link>
      <guid>https://dev.to/stegriff/converting-an-ancient-angular-11-app-to-vue-3-with-github-copilot-2c39</guid>
      <description>&lt;p&gt;Every week Dependabot sends me security advisories about my most vulnerable, out of date GitHub repos, and every week that &lt;a href="https://stegriff.co.uk/upblog/age-of-mythology-board-game-helper-in-angular/" rel="noopener noreferrer"&gt;Age of Mythology Board Game Helper&lt;/a&gt; I once wrote in Angular tops the list by a mile. How many packages are in the Angular ecosystem?? How can there be so many and so vulnerable??&lt;/p&gt;

&lt;p&gt;I still want the app to exist. But I want the warnings to stop.&lt;/p&gt;

&lt;p&gt;I briefly tried upgrading it to a newer Angular version but that was a nightmare, and it wouldn’t solve the long term problem when new versions were later deemed to be vulnerable.&lt;/p&gt;

&lt;p&gt;I wanted to rewrite it from Angular, which I don’t know or use at all, into a tech I know and enjoy, like Vue 3. This sounded like a lot of drone work though so I got Claude involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The LLM rewrite
&lt;/h2&gt;

&lt;p&gt;I moved the angular app into a new subdir of the project, fittingly called &lt;code&gt;angular&lt;/code&gt; and made a new empty dir for &lt;code&gt;vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I used VS Code with GitHub CoPilot, with Claude Sonnet 4.5 selected, in Agent mode.&lt;/p&gt;

&lt;p&gt;My initial prompt was pretty simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Look at this Angular app. We are going to rewrite it in Vue 3. Keep it as simple as possible. The vue app does not need any unit testing. You will init the Vue app in the /vue dir, imitating the angular app in /angular. Please make a plan before starting.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#file:app.module.ts #file:main.ts #file:index.html&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It had some trouble figuring out what I’d done with the file dirs even though I explained it. I also have a perennial issue with GHCP failing to read results from terminal the first time, so I have to close all the magic terminals and tell it to try again.&lt;/p&gt;

&lt;p&gt;In the end I ran one or two commands on Claude’s behalf (I copied out the commands it had printed to the Agent panel) to stand up the Vue project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Course correction with GHCP
&lt;/h2&gt;

&lt;p&gt;I’m learning that when GHCP is waiting for input or authorisation to run a terminal command, I can interject. Like sometimes my npm version is wrong, and it’s about to go off on a wild tangent working around it, so I just say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve installed node 22.20.0 and run the npm create and npm i commands for you, please check the content of the Vue directory then continue&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other times it guesses super wrong on the source of an error, e.g.:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The issue is that the file uses “smart quotes” (curly apostrophes) in some strings which TypeScript is misinterpreting. Let me check the actual content by running type-check to see what the error really is:&lt;code&gt;npm run type-check 2&amp;gt;&amp;amp;1 | head -50&lt;/code&gt;The issue is with the apostrophes. It looks like when I copied the text, some apostrophes became smart quotes (‘ instead of ‘). Let me check the exact character:&lt;code&gt;sed -n '63p' /d/git/aom-bg/vue/src/data/allUnitsData.ts | od -c | head -3&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And while it’s waiting to run sed, I, a smol brain developer, interject:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No mate, some of the unit specialText entries contain apostrophes and you need to escape those&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In fairness, I don’t find any of this that strange, as many actual real human devs also love to overcomplicate solutions while missing the obvious (myself included).&lt;/p&gt;

&lt;p&gt;I think it’s been documented that Claude’s long declarations of victory are annoying. It will give you a looooong markdown report to prove that it’s finished its checklist and koded everyfing to spek.&lt;/p&gt;

&lt;p&gt;Unforunately, if it tried to &lt;code&gt;npm run dev&lt;/code&gt; and couldn’t get access to the output, it will declare victory even when it doesn’t build or run. So I did have to make it check its work and fix the actual errors:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please run npm run build and check output for errors. Please don’t declare victory any more, that’s a waste of tokens&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[N.b. it looks like I say “Please” to LLMs a lot. This is only partly due to my concern for appeasing our future robot overlords]&lt;/p&gt;

&lt;p&gt;After this fix it was done-ish…&lt;/p&gt;

&lt;h2&gt;
  
  
  The cleanup
&lt;/h2&gt;

&lt;p&gt;To keep it brief, here are the major shortfalls from Claude’s side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It copied some CSS into scoped styles where they didn’t need to be, and transcribed them wrong, changing 8.5rem to 10rem, for example. It also made up some colour codes that don’t exist in the original. I don’t really understand why.&lt;/li&gt;
&lt;li&gt;In general, LLMs have poor knowledge of the Tachyons CSS library, which I use for everything, so they tend to reinvent/duplicate some of its CSS classes, and hallucinate others.&lt;/li&gt;
&lt;li&gt;It didn’t clean up all the “Welcome” cruft that comes with a standard Vue boilerplate project.&lt;/li&gt;
&lt;li&gt;It inexplicably abstracted the name “Age of Mythology” from static text into a variable… just in case I ever wanted to change it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all pretty minor and I was happy.&lt;/p&gt;

&lt;p&gt;I think in future I am going to get LLMs to read the &lt;a href="https://tachyons.io/docs/table-of-styles/" rel="noopener noreferrer"&gt;Tachyons Table of Styles&lt;/a&gt; to avoid misunderstandings.&lt;/p&gt;

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

&lt;p&gt;Now we’re done with LLM talk and squarely into Angular vs Vue territory.&lt;/p&gt;

&lt;p&gt;With both variants built for production and shipped via Netlify:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;th&gt;Lighthouse&lt;/th&gt;
&lt;th&gt;HTML Size&lt;/th&gt;
&lt;th&gt;CSS Size&lt;/th&gt;
&lt;th&gt;JS Size&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Angular&lt;/td&gt;
&lt;td&gt;74/100/100&lt;/td&gt;
&lt;td&gt;201B&lt;/td&gt;
&lt;td&gt;10.5kB&lt;/td&gt;
&lt;td&gt;116.34kB*&lt;/td&gt;
&lt;td&gt;127.05kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue 3&lt;/td&gt;
&lt;td&gt;98/100/100&lt;/td&gt;
&lt;td&gt;70B&lt;/td&gt;
&lt;td&gt;40B†&lt;/td&gt;
&lt;td&gt;37.8kB&lt;/td&gt;
&lt;td&gt;37.91kB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Angular split the JS into three files, Vue only one. Vue did some route splitting, so that additional JS was downloaded on another route (&lt;code&gt;/unit/something&lt;/code&gt;) when needed.&lt;/p&gt;

&lt;p&gt;†This is slightly unfair, beacuse Angular uses scoped styles, which I rationalised for the Vue development, moving them into a common stylesheet. This likely reduced class size, specificity of name, and duplication.&lt;/p&gt;

&lt;p&gt;Development file size without packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular: 582 kB &lt;/li&gt;
&lt;li&gt;Vue: 209 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am not measuring with packages because I don’t have the patience or disk space for an &lt;code&gt;npm install&lt;/code&gt; on a 5-year old Angular project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The new shiny Vue version is now live at &lt;a href="https://aom.netlify.app" rel="noopener noreferrer"&gt;https://aom.netlify.app&lt;/a&gt; and you can click around it right now.&lt;/p&gt;

&lt;p&gt;I like it. It feels snappy and it looks the same as before. ⚔&lt;/p&gt;

&lt;p&gt;If you want to check out the &lt;a href="https://github.com/stegriff/aom-bg" rel="noopener noreferrer"&gt;final PR porting to Vue&lt;/a&gt;, you can do so.&lt;/p&gt;

&lt;p&gt;We talked frontend frameworks the other day at work, and it strikes me that frontend is now just a lot more portable in the age of LLMs, when they’re this quick and capable at a total rewrite.&lt;/p&gt;

&lt;p&gt;Let’s remember to &lt;a href="https://stegriff.co.uk/upblog/sept-2025-month-notes/" rel="noopener noreferrer"&gt;use AI thoughtfully&lt;/a&gt;, weighing its positives and negatives, accepting economic value where it’s real, and keeping those human brains exercised and working! 🧠🏋️&lt;/p&gt;

</description>
      <category>vue</category>
      <category>ai</category>
      <category>angular</category>
      <category>github</category>
    </item>
    <item>
      <title>Keeping an ASP.Net Core/6/7/8 app alive in IIS</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Mon, 11 Dec 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/keeping-an-aspnet-core678-app-alive-in-iis-m2f</link>
      <guid>https://dev.to/stegriff/keeping-an-aspnet-core678-app-alive-in-iis-m2f</guid>
      <description>&lt;p&gt;There are three things you have to do.&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://learn.microsoft.com/en-us/iis/configuration/system.webserver/applicationinitialization/#windows-server-2012-or-windows-server-2012-r2" rel="noopener noreferrer"&gt;install the ‘Application Initialization’&lt;/a&gt; IIS feature on the server! This caught me out.&lt;/p&gt;

&lt;p&gt;Next, configure your &lt;code&gt;applicationInitialization&lt;/code&gt; settings in &lt;code&gt;web.config&lt;/code&gt; or IIS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;system.webServer&amp;gt;
    &amp;lt;applicationInitialization doAppInitAfterRestart="true"&amp;gt;
      &amp;lt;add initializationPage="/some-route" hostName="hostname.example.com" /&amp;gt;
    &amp;lt;/applicationInitialization&amp;gt;
  &amp;lt;/system.webServer&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;The hostname property doesn’t need a scheme (like &lt;code&gt;https://&lt;/code&gt;); just the raw hostname.&lt;/p&gt;

&lt;p&gt;I believe the &lt;code&gt;initializationPage&lt;/code&gt; can be the root route (&lt;code&gt;/&lt;/code&gt;) but it might be best to specify a page/file name. The docs say the default value is &lt;code&gt;""&lt;/code&gt; but that doesn’t mean it would work…&lt;/p&gt;

&lt;p&gt;Lastly, in the Application Pool settings for the target app pool, in Advanced Settings, set Start Mode to &lt;code&gt;AlwaysRunning&lt;/code&gt; and the Idle Time-out to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See the excellent Hangfire docs for &lt;a href="https://docs.hangfire.io/en/latest/deployment-to-production/making-aspnet-app-always-running.html#making-asp-net-core-application-always-running-on-iis" rel="noopener noreferrer"&gt;Making ASP.NET Core application always running on IIS&lt;/a&gt;. The only part not covered there is installing the server role! 😩&lt;/p&gt;

&lt;p&gt;Have fun out there ☔&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Accessing getters or actions from another Vuex module</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Fri, 07 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/accessing-getters-or-actions-from-another-vuex-module-179m</link>
      <guid>https://dev.to/stegriff/accessing-getters-or-actions-from-another-vuex-module-179m</guid>
      <description>&lt;p&gt;This is a cheatsheet. Say you have multiple named modules in your Vuex store and you need to access something from more than one of them at a time, or they need to talk to each other. How do we do that?&lt;/p&gt;

&lt;p&gt;For these demos, imagine a store with modules called &lt;code&gt;trees&lt;/code&gt;, &lt;code&gt;seasons&lt;/code&gt;, and &lt;code&gt;weather&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;trees&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/trees&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;seasons&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/seasons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;weather&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/weather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;trees&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Any getter from a component
&lt;/h2&gt;

&lt;p&gt;The easy part. You can &lt;code&gt;mapGetters&lt;/code&gt; from one or more different modules in the &lt;code&gt;computed&lt;/code&gt; section of a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mapGetters&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vuex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyComponent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;mapGetters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trees&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isEvergreen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;mapGetters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seasons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isTropicalSeason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isRainySeason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Here we map the &lt;code&gt;trees.isEvergreen&lt;/code&gt;, &lt;code&gt;seasons.isTropicalSeason&lt;/code&gt;, and &lt;code&gt;seasons.isRainySeason&lt;/code&gt; getters.&lt;/p&gt;

&lt;p&gt;The first param to &lt;code&gt;mapGetters&lt;/code&gt; is module name, the second is an array of getter names, all as strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any getter from an action
&lt;/h2&gt;

&lt;p&gt;The first param of every &lt;code&gt;action&lt;/code&gt; is the whole &lt;code&gt;store&lt;/code&gt;, and it is normal to destructure this using braces to access the members you need, which can include &lt;code&gt;getters&lt;/code&gt; (for same module) and &lt;code&gt;rootGetters&lt;/code&gt; (for accessing any module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// An action:&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;tryTreeGrowth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootGetters&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;sapling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;growingConditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getGrowingConditions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;rootGetters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weather/getWeeklyWeather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;rootGetters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seasons/getSeason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;sapling&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;growingConditions&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DO_GROWTH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sapling&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Any getter from another getter
&lt;/h2&gt;

&lt;p&gt;If you’re doing this, it’s probably a mistake. You should probably abstract this “one level up” in your component or action; grabbing the various values you need from each module and combining them there.&lt;/p&gt;

&lt;p&gt;However, there is a way. &lt;a href="https://vuex.vuejs.org/api/#getters" rel="noopener noreferrer"&gt;On a getter, there are four fixed parameters&lt;/a&gt; (they don’t use destructuring like actions do): &lt;code&gt;(state, getters, rootState, rootGetters)&lt;/code&gt;. If you don’t need to use one or more of them, you can “throw them away” by using a variable name that’s a number of underscores. This will stop eslint/prettier complaining about unused variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Getter in the weather module:&lt;/span&gt;
&lt;span class="nf"&gt;isWeatherSeasonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootGetters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isRainy&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rootGetters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seasons/isRainySeason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDry&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rootGetters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seasons/isDrySeason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We have &lt;code&gt;_&lt;/code&gt; in place of &lt;code&gt;state&lt;/code&gt;, and &lt;code&gt;__&lt;/code&gt; in place of &lt;code&gt;rootState&lt;/code&gt;, because neither of those is used in the function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any action from another action
&lt;/h2&gt;

&lt;p&gt;When you destructure the store as a parameter of an action, you can grab the &lt;code&gt;dispatch&lt;/code&gt; method. Dispatch allows you to call any action from any module, using a name with a slash, as long as you &lt;a href="https://vuex.vuejs.org/guide/modules.html#accessing-global-assets-in-namespaced-modules" rel="noopener noreferrer"&gt;pass a third parameter&lt;/a&gt; (options) with &lt;code&gt;{ root: true }&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Action in the weather module:&lt;/span&gt;
&lt;span class="nf"&gt;resetClimate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Also reset climate in the 'seasons' module&lt;/span&gt;
  &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seasons/resetClimate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the example code above, the action takes no input, it’s just a “do this”.&lt;/p&gt;

&lt;p&gt;Alright, have fun out there 🦥🍒&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Wrap a callback-based JS library with Promises</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Thu, 13 Apr 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/wrap-a-callback-based-js-library-with-promises-4e29</link>
      <guid>https://dev.to/stegriff/wrap-a-callback-based-js-library-with-promises-4e29</guid>
      <description>&lt;p&gt;I’ve been integrating the Microsoft Cognitive Services Text-to-speech thingy, to make a little web API that can speak Vietnamese.&lt;/p&gt;

&lt;p&gt;Take a look at &lt;a href="https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/get-started-text-to-speech?tabs=windows%2Cterminal&amp;amp;pivots=programming-language-javascript" rel="noopener noreferrer"&gt;this sample code&lt;/a&gt; for using the &lt;code&gt;microsoft-cognitiveservices-speech-sdk&lt;/code&gt; npm package (I’ve snipped some stuff for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;microsoft-cognitiveservices-speech-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speechConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SpeechConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_REGION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AudioConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAudioFileOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio.wav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The language of the voice that speaks.&lt;/span&gt;
&lt;span class="nx"&gt;speechConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speechSynthesisVoiceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US-JennyNeural&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="c1"&gt;// Create the speech synthesizer.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SpeechSynthesizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;speechConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You have selected Microsoft Sam as the Computer's default voice.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Start the synthesizer and wait for a result.&lt;/span&gt;
&lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speakTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResultReason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SynthesizingAudioCompleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthesis finished.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Speech synthesis canceled, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorDetails&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;err - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Now synthesizing to: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



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

&lt;p&gt;The example &lt;code&gt;synthesizer&lt;/code&gt; object only produces values in its callbacks.&lt;/p&gt;

&lt;p&gt;If you wrap this code sample in a &lt;code&gt;speak(text)&lt;/code&gt; function, then it will return before the values you want are “ready”. You can’t directly &lt;code&gt;return&lt;/code&gt; the outcome; you’d have to either match its old-school vibes and pass a &lt;code&gt;cb&lt;/code&gt; callback argument, or if you were hoisting it into express, perhaps you’d pass in the express &lt;code&gt;response&lt;/code&gt; object so that your &lt;code&gt;speak&lt;/code&gt; function can directly return a web response.&lt;/p&gt;

&lt;p&gt;Rubbish solutions!!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ObviouslY&lt;/em&gt; we want to wrap it in a Promise so that we can provide a dead simple awaitable interface for an async route handler to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/create/:text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trading callbacks for a Promise
&lt;/h2&gt;

&lt;p&gt;But how do we get a Promise out of this mess?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrap the insides of the new function in &lt;code&gt;return new Promise((resolve, reject) =&amp;gt; { ... }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anywhere you want to return a success, invoke &lt;code&gt;resolve(someResponseObject)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anywhere you want to handle an error, invoke &lt;code&gt;reject(someErrorObject)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Your new method is then-able and async!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio.wav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unable to connect to speech server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Connect SDK&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speechConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SpeechConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPEECH_REGION&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AudioConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAudioFileOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;speechConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speechSynthesisVoiceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US-JennyNeural&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

      &lt;span class="c1"&gt;// Create the speech synthesizer.&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SpeechSynthesizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;speechConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speakTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ResultID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resultId&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nx"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Isn’t that cool?&lt;/p&gt;

&lt;p&gt;As usual, I basically got this all from the great &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#examples" rel="noopener noreferrer"&gt;Promise examples on MDN&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wish you growth and harmony this summertime 🌼&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Is .Net imitating Node.js?</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Fri, 01 Jul 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/is-net-imitating-nodejs-5ca2</link>
      <guid>https://dev.to/stegriff/is-net-imitating-nodejs-5ca2</guid>
      <description>&lt;p&gt;Recently .Net has had big changes inside and out. On the inside, the “my first program” in C# and .Net is a lot shorter than it was. And on the outside, .Net is now on a yearly major version release schedule.&lt;/p&gt;

&lt;p&gt;This is a bit of a personal theory incoming, but I think it all bears out in reality… hear me out…&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Node.js like? 🧐
&lt;/h2&gt;

&lt;h3&gt;
  
  
  It’s quick to get started 🏎
&lt;/h3&gt;

&lt;p&gt;Your boilerplate web API project with express in Node.js is a very simple thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) =&amp;gt; {
  res.send('Hello World!')
})

app.listen(port, () =&amp;gt; {
  console.log(`Example app listening on port ${port}`)
})

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

&lt;/div&gt;



&lt;p&gt;…and anyone with a bit of JavaScript can jump into a &lt;a href="https://glitch.com/create-project" rel="noopener noreferrer"&gt;new Glitch project&lt;/a&gt;, read a few docs, and write a live web API in minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/abgxkEiJQjaSY/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/abgxkEiJQjaSY/giphy.gif" alt="Retro-futuristic Japanese car concept" width="500" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You can make powerful web projects very quickly 💪
&lt;/h3&gt;

&lt;p&gt;For years Node.js has been letting hacker types throw together amazing web projects in a short time [citation needed].&lt;/p&gt;

&lt;p&gt;Speaking from personal experience, if I wanted to &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://glitch.com/~kinopio-text" rel="noopener noreferrer"&gt;write an API to take text and render it as an SVG&lt;/a&gt; or &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://glitch.com/~img-swatch" rel="noopener noreferrer"&gt;return the most prevalent colours in an image&lt;/a&gt; or perhaps &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://glitch.com/~ziposcure" rel="noopener noreferrer"&gt;obscure the origin of a zip file and present it in the browser as a docx&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...then I can knock that together in Node in 1 or 2 hours. These are things that would take at least a day of fiddling in .Net.&lt;/p&gt;

&lt;p&gt;In fairness, some of this is down to &lt;code&gt;npm&lt;/code&gt; vs &lt;code&gt;nuget&lt;/code&gt; package ecosystems. But it’s a chicken-egg thing. The more users you have in the ecosystem, the more high quality packages you have, the more devs you attract, 🔁...&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s always relentlessly updating 📦📦📦
&lt;/h3&gt;

&lt;p&gt;NodeJS version schedule will make your head spin. You thought you were up to date because you just got onto NodeJS 12. I mean, it sounds like a big number. GUESS AGAIN!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://nodejs.org/en/about/releases/" rel="noopener noreferrer"&gt;NodeJS release cadence&lt;/a&gt; features a new major version number every six months!&lt;/p&gt;

&lt;p&gt;Importantly, &lt;strong&gt;even-numbered versions get Long-Term Support (LTS)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So don’t even bother with the odd ones.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Node.js 19 comes out this November and is supported til June 2023 (8 months total)&lt;/li&gt;
&lt;li&gt;Node.js 20 comes out April 2023 and gets 3 whole years of support, til April 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why would .Net want to change? 🤔
&lt;/h2&gt;

&lt;p&gt;Here’s a bit of guesswork:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starting a new .Net project means loads of boilerplate and some instant turn-offs&lt;/li&gt;
&lt;li&gt;It’s hard for beginners to build things in .Net&lt;/li&gt;
&lt;li&gt;People (who?) like the easy setup Node projects&lt;/li&gt;
&lt;li&gt;JavaScript ecosystem has a reputation for being the “it” thing (probably mistakenly)&lt;/li&gt;
&lt;li&gt;Someone at Microsoft thinks imitating the Node.js way would be a good idea&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What would it look like to copy these attributes? 📋
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/VVcvfnnLHni5G/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/VVcvfnnLHni5G/giphy.gif" alt="A fluffy white cat copying a lucky waving cat toy" width="728" height="910"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well maybe you would:&lt;/p&gt;

&lt;h3&gt;
  
  
  Make empty projects simpler ✅
&lt;/h3&gt;

&lt;p&gt;The new “hello world” console app has gone from 11 lines to 1 (and while it used to feature six keywords, &lt;code&gt;using, namespace, class, static, void, and string&lt;/code&gt;, it now features… none)&lt;/p&gt;

&lt;p&gt;Your most basic web API project has gone from 22 lines to 4 (and from looking like a birds nest to looking like understandable code)&lt;/p&gt;

&lt;p&gt;This article, &lt;a href="https://lee-jdale.medium.com/net-6-minimal-apis-vs-node-js-3d2f00644230" rel="noopener noreferrer"&gt;.Net 6 Minimal APIs vs Node JS&lt;/a&gt; covers it way better than I want to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release more often 🚀
&lt;/h3&gt;

&lt;p&gt;Meet the new &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core" rel="noopener noreferrer"&gt;.Net release schedule&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Major version release every 12 months&lt;/li&gt;
&lt;li&gt;Even-numbered versions are LTS for 3 years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds familiar.&lt;/p&gt;

&lt;p&gt;Not quite the same frightening clip as Node maintains, but we had .Net Framework 4.x for… (checks watch)… 12 years – so this is a real leap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this maybe a good or not so good idea 🎭
&lt;/h2&gt;

&lt;p&gt;Copying Node.js in 2021/2, as if it’s the hotness, feels like an anachronism ten years out of place. Like saying “we should be getting into DVDs!” in 2012.&lt;/p&gt;

&lt;p&gt;Nonetheless – what they’re imitating might be good ideas, regardless of time and provenance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular rather than irregular release schedule, which &lt;a href="https://umbraco.com/blog/umbraco-10-release/" rel="noopener noreferrer"&gt;allows downstream consumers to make predictable decisions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Easier, more accessible language features for newbies means more devs, more packages, more community (as mentioned previously).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up 🍬
&lt;/h2&gt;

&lt;p&gt;This article has ignored a whole category of other design leanings to do with running .Net on Linux and the cloud and stuff. But I wanted to focus on just these two things.&lt;/p&gt;

&lt;p&gt;So, that’s my headcanon about why MS is doing these things with .Net. I hope you enjoyed it. What do you think?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Palace of Palettes with Appwrite</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Thu, 12 May 2022 20:42:55 +0000</pubDate>
      <link>https://dev.to/stegriff/a-palace-of-palettes-with-appwrite-3ba5</link>
      <guid>https://dev.to/stegriff/a-palace-of-palettes-with-appwrite-3ba5</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;My submission for the &lt;a href="https://dev.to/devteam/announcing-the-appwrite-hackathon-on-dev-1oc0"&gt;Appwrite Hackathon&lt;/a&gt; is &lt;a href="https://palace.sign.me.uk/" rel="noopener noreferrer"&gt;&lt;strong&gt;Palace&lt;/strong&gt;&lt;/a&gt;, a palette organising playground for people dealing with colour &lt;em&gt;at palatial scale&lt;/em&gt; 😎🤪&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/stegriff/palace" rel="noopener noreferrer"&gt;🐙 GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://palace.sign.me.uk/" rel="noopener noreferrer"&gt;🏯 Live Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is borne out of my need for a tool to let me import, tweak, and export lots of palettes. And I thought it would be a great opportunity to try out the auth and database features of Appwrite for this hackathon!&lt;/p&gt;

&lt;p&gt;The Appwrite features it uses are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anonymous sessions&lt;/li&gt;
&lt;li&gt;Accounts&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can tell I've only scratched the surface, as there's a lot of backend support in Appwrite too, which was just outside the scope of this project.&lt;/p&gt;

&lt;p&gt;But it was great to be able to add server-backed save features for relatively little effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Web2 Wizards&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SteGriff" rel="noopener noreferrer"&gt;
        SteGriff
      &lt;/a&gt; / &lt;a href="https://github.com/SteGriff/palace" rel="noopener noreferrer"&gt;
        palace
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Palette Ace
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;palace&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Palette Ace - Build, curate, and export colour palettes&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Resources&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Appwrite &lt;code&gt;http://134.122.50.123/console&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.dev/appwrite/demo-todo-with-vue/blob/main/src/api/index.js" rel="nofollow noopener noreferrer"&gt;Demo todo with vue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SteGriff/ste-vue-3" rel="noopener noreferrer"&gt;Built from Ste's Vue3 starter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kinopio.club/shade-toucan-9fGD4TQJJgFIBQ_L0YCk_" rel="nofollow noopener noreferrer"&gt;Kinopio board (private)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Dev&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Install dependencies with &lt;code&gt;npm i&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run with &lt;code&gt;npm run start&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prod&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Build for prod with &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy to e.g. Netlify with the output from &lt;code&gt;build&lt;/code&gt; directory&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SteGriff/palace" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;I &lt;a href="https://dev.to/stegriff/walking-appwrite-starting-my-appwrite-project-4ikn"&gt;wrote about the creation process&lt;/a&gt; in a previous post, but the tall and short of it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built with &lt;a href="https://github.com/SteGriff/ste-vue-3" rel="noopener noreferrer"&gt;my Vue3 starter&lt;/a&gt; and &lt;a href="https://pinia.vuejs.org/introduction.html#basic-example" rel="noopener noreferrer"&gt;Pinia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Made lots of use of the great Appwrite &lt;a href="https://github.com/appwrite/demo-todo-with-vue/" rel="noopener noreferrer"&gt;demo-todo-with-vue&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Used packages like &lt;a href="https://aesoper101.github.io/vue3-colorpicker/" rel="noopener noreferrer"&gt;vue3-colorpicker&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/colord" rel="noopener noreferrer"&gt;colord&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deployed the backend on DigitalOcean with the &lt;a href="https://marketplace.digitalocean.com/apps/appwrite" rel="noopener noreferrer"&gt;Appwrite one-click droplet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deployed the UI on Netlify&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Challenges
&lt;/h3&gt;

&lt;p&gt;I wanted Palace to have import/export to JSON and CSS, but didn't have time inside the hackathon. Those will come later 😊&lt;/p&gt;

&lt;p&gt;AI had to use a spare domain to get the backend talking to the frontend over HTTPS; that was an unexpected challenge but it worked eventually.&lt;/p&gt;

&lt;p&gt;Palace has one &lt;strong&gt;really big bug&lt;/strong&gt; which is that data isn't isolated per user... everyone can edit everyone's data 😅... I set everything up as I thought I should in appwrite, and manually set permissions with &lt;code&gt;user:$userId&lt;/code&gt; but still, the data leaks. Maybe somebody will comment and tell me what I did wrong!&lt;/p&gt;

&lt;p&gt;Thanks for reading my entry -- happy hacking 😊&lt;/p&gt;

</description>
      <category>appwritehack</category>
    </item>
    <item>
      <title>Walking Appwrite - Starting my Appwrite project</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Tue, 19 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/walking-appwrite-starting-my-appwrite-project-4ikn</link>
      <guid>https://dev.to/stegriff/walking-appwrite-starting-my-appwrite-project-4ikn</guid>
      <description>&lt;p&gt;I saw &lt;a href="https://dev.to/devteam/announcing-the-appwrite-hackathon-on-dev-1oc0"&gt;this Appwrite hackathon&lt;/a&gt; on dev.to and I guess I was taken in by the big numbers or the promise of a free t-shirt, but one way or another I thought “I could do that!” and so now I’m doing that.&lt;/p&gt;

&lt;p&gt;While developing my current project I realised I could use a tool that lets you manage palettes at scale. A lot of the existing things out there let you work with one palette at a time in a very arty, considered way – I need to create, update, and delete palettes with a broad view. I want to import and export lots of JSON. And export CSS too. So this seemed like an opportunity to build that, and get rewarded at the same time.&lt;/p&gt;

&lt;p&gt;It’s called ‘palace’ (palette ace, see).&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting up Appwrite
&lt;/h2&gt;

&lt;p&gt;Appwrite is a backend-as-a-service, and it’s delivered as a Docker… crate? Is that what they’re called? I &lt;a href="https://dev.to/upblog/learning-to-use-docker/"&gt;played with Docker&lt;/a&gt; one time but I’m not confident with it, plus, I want to create an app that’s Truly Live On The Internet and I’m not gonna go through exposing my local PC and opening firewall holes, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.digitalocean.com/apps/appwrite" rel="noopener noreferrer"&gt;Appwrite is a one-click app on DigitalOcean&lt;/a&gt;. So, it seemed like a chance to try out DigitalOcean. I splashed $5 prepayment on a “small droplet” for a month – the hackathon deadline is a month away anyway. Nice.&lt;/p&gt;

&lt;p&gt;Suddenly the amount of new information and details to remember was proliferating rapidly, bees circling my head, seeking a place to nest. I put the bees to bed in a tidy new &lt;a href="https://kinopio.club" rel="noopener noreferrer"&gt;Kinopio&lt;/a&gt; space (pictured) as it helps me group and organise details and useful links.&lt;/p&gt;

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

&lt;p&gt;I’ve gotta say the one-click experience on DO was really easy. And after it comes up, you get a big button to take you to your Appwrite dashboard. It gets a public IPv4 address (a rare commodity!) and you can assign a custom domain if you want.&lt;/p&gt;

&lt;p&gt;Looking at Appwrite features, I thought the instant account management would be fun to use. Accounts and auth are always a bit of a pain so this seems like a handy feature to leverage.&lt;/p&gt;

&lt;p&gt;The thing that struck me right away was “Anonymous Sessions”. I love tools with anonymous sessions! You can just get to trying it without creating an account, and then promote your account into a real one later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;I’m learning Vue3 with the Composition API, &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;, Typescript, etc. Appwrite provide a useful sample project called &lt;a href="https://github.com/appwrite/demo-todo-with-vue/" rel="noopener noreferrer"&gt;demo-todo-with-vue&lt;/a&gt;, so I keep the &lt;a href="https://github.com/appwrite/demo-todo-with-vue/" rel="noopener noreferrer"&gt;readme&lt;/a&gt; open in one tab and a &lt;a href="https://github.dev/appwrite/demo-todo-with-vue/blob/main/src/api/index.js" rel="noopener noreferrer"&gt;GitHub Dev window of the source code&lt;/a&gt; open in another.&lt;/p&gt;

&lt;p&gt;The most useful thing here was their &lt;code&gt;api.js&lt;/code&gt; wrapper around the API. It made it simpler to understand how to use the underlying Appwrite library.&lt;/p&gt;

&lt;p&gt;I had some trouble picking a good colour picker, and settled on &lt;a href="https://aesoper101.github.io/vue3-colorpicker/" rel="noopener noreferrer"&gt;vue3-colorpicker&lt;/a&gt;, but I had to do some CSS hacks to get it to display where I wanted.&lt;/p&gt;

&lt;p&gt;Also troublesome was the npm &lt;code&gt;color&lt;/code&gt; package, which doesn’t play nice with Typescript – fortunately someone made &lt;a href="https://www.npmjs.com/package/colord" rel="noopener noreferrer"&gt;colord&lt;/a&gt; to solve this problem.&lt;/p&gt;

&lt;p&gt;Lastly, I realised pretty quick I need some global state management. I usually steer away from a redux store unless it’s absolutely necessary, because of all the extra code you have to write. To my pleasant surprise, &lt;a href="https://pinia.vuejs.org/introduction.html#basic-example" rel="noopener noreferrer"&gt;pinia&lt;/a&gt; (the replacement for Vuex as of Vue3) is simpler than it’s predecessor, and they’ve completely done away with &lt;code&gt;Mutations&lt;/code&gt;, which were gross. So I’m really happy. Using it is a breeze! 🎑&lt;/p&gt;

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

&lt;p&gt;I have a test instance on Netlify… URL secret for now 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Progress
&lt;/h2&gt;

&lt;p&gt;So far, I’ve got it to get or create an anonymous session, and I’ve investigated saving data back to Appwrite. I didn’t enjoy Appwrite’s interface for setting up “collection attributes” (database columns) - it is a pretty painful click-and-wait web UI. I can’t imagine architecting a large database with that tool, I guess (hope) there is some kind of bulk interface where you can upload a YAML or something.&lt;/p&gt;

&lt;p&gt;I’m not sure whether I want to use the traditional “records” concept for each palette, or whether to store the user’s entire app state into an unstructured document and just sync that. The latter seems easier to acheive because my CRUD operations aren’t as clearly defined as in the trivial todo app sample.&lt;/p&gt;

&lt;p&gt;So, I’ve got some technical choices to make and some work to do. But I’m having fun 😄 See you later!&lt;/p&gt;

</description>
      <category>appwritehack</category>
    </item>
    <item>
      <title>Super-powered Wikis are Categorical, not Hierarchical!</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/super-powered-wikis-are-categorical-not-hierarchical-3nka</link>
      <guid>https://dev.to/stegriff/super-powered-wikis-are-categorical-not-hierarchical-3nka</guid>
      <description>&lt;p&gt;When we build corporate Wikis and shared knowledge bases, it's tempting to try to neatly arrange every page into a master hierarchy, or tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Products
  Software
    AcmeQuery
      Setup
      Environments
      Deployment
Techniques
  Project Setup
Tools
  DeploymentWidget

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

&lt;/div&gt;



&lt;p&gt;It’s a nice idea, but you will quickly run into one of the following problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Not every article has exactly one place where it should live&lt;/li&gt;
&lt;li&gt;Users might not feel confident to add new knowledge if they can’t find the right place for it to live&lt;/li&gt;
&lt;li&gt;“Designed” hierarchies represent a fixed-in-time purpose in a changing domain&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What are the goals for your wiki?
&lt;/h2&gt;

&lt;p&gt;Here are my guesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to contribute to&lt;/li&gt;
&lt;li&gt;Easy to find what you’re looking for&lt;/li&gt;
&lt;li&gt;Comprehensive of your contributors’ combined knowledge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How can we acheive these things?&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider the world’s largest wiki…
&lt;/h2&gt;

&lt;p&gt;Wikipedia. Your corporate wiki will not grow to the size of Wikipedia. But we can learn lessons there about large-scale organisation of information.&lt;/p&gt;

&lt;p&gt;When you go on Wikipedia, do you see pages in folders and subfolders? Do you “drill down” to the page you’re looking for? Probably not; it’s driven by &lt;strong&gt;Searchability&lt;/strong&gt; and &lt;strong&gt;Categorisation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Look at this footer from the Wikipedia article for &lt;a href="https://en.wikipedia.org/wiki/FileMaker" rel="noopener noreferrer"&gt;FileMaker&lt;/a&gt;, a popular low-code platform:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36613ktsj5ej4mqmb0bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36613ktsj5ej4mqmb0bs.png" alt="Footer of a Wikipedia page showing categories and template boxes" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page is in 12 different categories. If the site were a hierarchy, where would you put ‘FileMaker’?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Home -&amp;gt; Technology -&amp;gt; Computing -&amp;gt; Computer Data -&amp;gt; Databases -&amp;gt; Desktop database application development tools ?
Home -&amp;gt; Technology -&amp;gt; Software -&amp;gt; Software by operating system -&amp;gt; Cross-platform software ?

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

&lt;/div&gt;



&lt;p&gt;(n.b. these navs aren’t contrived; they’re taken from actual category navigation)&lt;/p&gt;

&lt;p&gt;It belongs in both of these places and more.&lt;/p&gt;

&lt;p&gt;So, the flexible, usable, discoverable solution for this page is that you can find it using any of the above, or by title, or by page content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You will&lt;/strong&gt; end up with pages that could live in two or more places: An analysis of entities that are used by multiple apps or have multiple representations; A supporting tool that you use to do various things; A setup or technique guide that applies to several areas; A glossary that allows cross-team communication; infrastructure stuff; environment stuff; shared features; third-party integrations, etc., etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for us
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Folksonomy" rel="noopener noreferrer"&gt;Folksonomy&lt;/a&gt; is an idea whereby your “folk” create and organise tags in a grassroots way. It can empower them to create content without fear of miscategorisation, and to own and organise the content as a team.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;My personal opinion&lt;/em&gt; is that hierarchies set in stone by forerunners and bosses are more likely to inhibit new submissions.&lt;/p&gt;

&lt;p&gt;Remember that most wikis allow you to freely move, rename, and recategorise content. It’s digital jazz, man. 🎷&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;My gold standard for super-powered internal wikis is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A wiki is a “big pile of pages”&lt;/li&gt;
&lt;li&gt;Anyone can make a page at any time if they think they know something that other people don’t know&lt;/li&gt;
&lt;li&gt;Try your best to give pages good titles which are searchable&lt;/li&gt;
&lt;li&gt;Try your best to apply relevant tags that you’ve seen around the place, and invent new tags when necessary, on new and existing content&lt;/li&gt;
&lt;li&gt;Add ‘See Also’ links between related pages (perhaps your software can do this automagically)&lt;/li&gt;
&lt;li&gt;Give visitors a “jumping off point” on Home, where they can go quickly to high-quality, highly-connected articles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next time
&lt;/h2&gt;

&lt;p&gt;Someone once said to me “If you want to write a wiki page, fine, but you’ll be responsible for keeping the (entire) wiki up-to-date!”. I think this misassumption is why said person had never contributed to their wiki.&lt;/p&gt;

&lt;p&gt;I’ve got news for you – beautiful and freeing – wikis don’t need to be up-to-date!&lt;/p&gt;

&lt;p&gt;But that’s for another time.&lt;/p&gt;

&lt;p&gt;Happy tagging 🌱📃✨&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>practices</category>
      <category>process</category>
      <category>teamwork</category>
    </item>
    <item>
      <title>Very Alternative Search Engines</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Thu, 16 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/very-alternative-search-engines-8o9</link>
      <guid>https://dev.to/stegriff/very-alternative-search-engines-8o9</guid>
      <description>&lt;p&gt;I think a lot of you already know about the usual alternatives to Google like &lt;a href="https://www.ecosia.org/" rel="noopener noreferrer"&gt;Ecosia&lt;/a&gt; (the search engine that plants trees) and &lt;a href="https://duckduckgo.com/" rel="noopener noreferrer"&gt;DuckDuckGo&lt;/a&gt; (the search engine that doesn’t track you).&lt;/p&gt;

&lt;p&gt;I’d like to explore some &lt;em&gt;very&lt;/em&gt; alternative search engines that I’ve found recently.&lt;/p&gt;

&lt;p&gt;These are good when you’re looking for niche results that are too hard to find with the usual suspects.&lt;/p&gt;

&lt;p&gt;In no particular order:&lt;/p&gt;

&lt;h2&gt;
  
  
  Mojeek
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.mojeek.com/" rel="noopener noreferrer"&gt;https://www.mojeek.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mojeek is UK based, human-centric, tracking-free, and they have their own crawler (contrast to DDG which piggy-backs off crawlers like Yandex)&lt;/p&gt;

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

&lt;p&gt;It seems fine. It finds this site so I’m happy 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  MillionShort
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://millionshort.com/" rel="noopener noreferrer"&gt;https://millionshort.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Search the web without the top &lt;code&gt;n&lt;/code&gt; websites (where n is 100 to 1 million). Super useful when you want to find something that’s not on Pinterest, Amazon, Buzzfeed, or one of the other Big Content Walled Gardens.&lt;/p&gt;

&lt;p&gt;You also get more filtering tools after search, like Date and Location, and you can re-introduce domains one-by-one if you think they may have relevant results.&lt;/p&gt;

&lt;p&gt;This helped me find the &lt;a href="https://dev.to/upblog/common-mistakes-in-english/"&gt;Common mistakes in English site after I lost it for a few years&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marginalia Search
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://search.marginalia.nu/" rel="noopener noreferrer"&gt;https://search.marginalia.nu/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just found this one today and it prompted this post. It has its own crawler, called Edge (lol).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The search engine calculates a score that aggressively favors text-heavy websites, and punishes those that have too many modern web design features.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However it does that, I’m pretty sure it excludes &lt;em&gt;this website&lt;/em&gt;. Which is weird because it’s pretty text heavy and not that modern, and Marginalia happens to include a few WordPress sites (which seem to be a counterexample of the above).&lt;/p&gt;

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

&lt;p&gt;I searched for “yamaha pss a50 fix” and it returned one result, which was some poor &lt;code&gt;.inf&lt;/code&gt; file from somewhere.&lt;/p&gt;

&lt;p&gt;Widening the net to “yamaha pss a50”, we get results from &lt;a href="http://sandsoftwaresound.net/" rel="noopener noreferrer"&gt;sandsoftwaresound.net&lt;/a&gt; (no complaints from me) but this site is excluded. I repeated the experiment with some other niche search terms which historically drive traffic to this blog and didn’t see myself. No offence taken, just… curious 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up 🗞
&lt;/h2&gt;

&lt;p&gt;Isn’t it weird that all three of these begin with ‘M’??&lt;/p&gt;

&lt;p&gt;Anyway, more niche search engines are a good thing! Tools for the job, fighting back against monopolies and hegemonies. That’s good stuff.&lt;/p&gt;

&lt;p&gt;Have you got any suggestions? Lmk in the comments! 👇&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Get hi-res twemoji SVGs by URL</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Thu, 09 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/stegriff/get-hi-res-twemoji-svgs-by-url-1d4d</link>
      <guid>https://dev.to/stegriff/get-hi-res-twemoji-svgs-by-url-1d4d</guid>
      <description>&lt;p&gt;Yo, just a quick one. If you want to get high quality renders of twitter emoji (twemoji), you can get the SVGs by codepoint with a URL like this: &lt;a href="https://twitter.github.io/twemoji/v/13.1.0/svg/1f439.svg" rel="noopener noreferrer"&gt;https://twitter.github.io/twemoji/v/13.1.0/svg/1f439.svg&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;To help you find codepoints, Emojipedia has a page, &lt;a href="https://emojipedia.org/emoji/" rel="noopener noreferrer"&gt;Every Emoji by Codepoint&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to lowercase any letters in the name.&lt;/li&gt;
&lt;li&gt;If an emoji is a combination of multiple points, separate them with a dash.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;E.g. For Woman Technologist (no skin colour), convert &lt;code&gt;U+1F469, U+200D, U+1F4BB&lt;/code&gt; to &lt;a href="https://twitter.github.io/twemoji/v/13.1.0/svg/1f469-200d-1f4bb.svg" rel="noopener noreferrer"&gt;1f469-200d-1f4bb.svg&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, check out the directory listing on GitHub: &lt;a href="https://github.com/twitter/twemoji/tree/gh-pages/v/13.1.0/svg" rel="noopener noreferrer"&gt;https://github.com/twitter/twemoji/tree/gh-pages/v/13.1.0/svg&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don’t hotlink to these images, there are CDNs for that, as explained on the &lt;a href="https://github.com/twitter/twemoji#readme" rel="noopener noreferrer"&gt;project readme&lt;/a&gt;. This is just a handy way to download an SVG so you can render it into an icon or use it in Inkscape, etc.&lt;/p&gt;

&lt;p&gt;Lastly, technically, these are CC-BY 4.0, so attribution is required 🙄🙄&lt;/p&gt;

&lt;p&gt;Have fun 🐦&lt;/p&gt;

</description>
      <category>svg</category>
      <category>emoji</category>
    </item>
    <item>
      <title>Copy rich HTML with the native Clipboard API 📋</title>
      <dc:creator>Ste Griffiths</dc:creator>
      <pubDate>Thu, 08 Jul 2021 15:57:15 +0000</pubDate>
      <link>https://dev.to/stegriff/copy-rich-html-with-the-native-clipboard-api-5ah8</link>
      <guid>https://dev.to/stegriff/copy-rich-html-with-the-native-clipboard-api-5ah8</guid>
      <description>&lt;p&gt;The relatively new Clipboard API in browsers lets you load up the user’s clipboard as though they’d copied something themselves. &lt;/p&gt;

&lt;p&gt;Copying text or images is fairly well documented, but examples writing &lt;strong&gt;rich text (as HTML)&lt;/strong&gt; are harder to come by.&lt;/p&gt;

&lt;p&gt;At time of writing, this is &lt;a href="https://www.chromestatus.com/feature/5357049665814528" rel="noopener noreferrer"&gt;implemented in Chrome 86+&lt;/a&gt; and &lt;a href="https://webkit.org/blog/10855/" rel="noopener noreferrer"&gt;in Safari&lt;/a&gt;. I got the content for this post from the &lt;a href="https://glitch.com/~hyper-silly-cross" rel="noopener noreferrer"&gt;Glitch project created by dsleeps at Google&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to copy rich text HTML onto the Clipboard API
&lt;/h2&gt;

&lt;p&gt;This sample assumes you have a &lt;code&gt;&amp;lt;div class="js-output"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; which contains your HTML to copy.&lt;/p&gt;

&lt;p&gt;I’ll cut right to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js-output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clipboardItemInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClipboardItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blobInput&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;clipboardItemInput&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle error with user feedback - "Copy failed!" kind of thing&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Key things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the HTML string (I’m using &lt;code&gt;innerHTML&lt;/code&gt; of an element for this)&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;Blob&lt;/code&gt;. 

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#parameters" rel="noopener noreferrer"&gt;Param one&lt;/a&gt; must be an Array-like or a &lt;code&gt;USVString&lt;/code&gt; value. So we wrap our HTML content in an array.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#parameters" rel="noopener noreferrer"&gt;Param two&lt;/a&gt; is an options object, where we set the MIME type.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Create a &lt;code&gt;ClipboardItem&lt;/code&gt; around the blob, specifying MIME type &lt;em&gt;again&lt;/em&gt;
&lt;/li&gt;

&lt;li&gt;Finally, write the &lt;code&gt;ClipboardItem&lt;/code&gt; to the clipboard API.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I have a quickly-made Vue app with a ‘Copy to Clipboard’ button at &lt;a href="https://stegriff.github.io/deployment-complete/" rel="noopener noreferrer"&gt;https://stegriff.github.io/deployment-complete/&lt;/a&gt;. Source repo at &lt;a href="https://github.com/stegriff/deployment-complete" rel="noopener noreferrer"&gt;https://github.com/stegriff/deployment-complete&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope this tutorial helps you! What will you make? 📋&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>dom</category>
      <category>html</category>
      <category>clipboard</category>
    </item>
  </channel>
</rss>
