<?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: Wardell Bagby</title>
    <description>The latest articles on DEV Community by Wardell Bagby (@wardellbagby).</description>
    <link>https://dev.to/wardellbagby</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%2F788748%2Fa53f17d8-4c4a-40a9-9a27-a771019b58bb.jpeg</url>
      <title>DEV Community: Wardell Bagby</title>
      <link>https://dev.to/wardellbagby</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wardellbagby"/>
    <language>en</language>
    <item>
      <title>So you want to know about Web Workers?</title>
      <dc:creator>Wardell Bagby</dc:creator>
      <pubDate>Fri, 21 Jan 2022 23:55:31 +0000</pubDate>
      <link>https://dev.to/wardellbagby/so-you-want-to-know-about-web-workers-4aoe</link>
      <guid>https://dev.to/wardellbagby/so-you-want-to-know-about-web-workers-4aoe</guid>
      <description>&lt;p&gt;Hey! Wardell here! So you've got yourself a website and you're interested in potentially off-loading some of your heavy-hitting computations to another thread?&lt;/p&gt;

&lt;p&gt;Sounds like you want a Web Worker!&lt;/p&gt;




&lt;h2&gt;
  
  
  Hold up, what even &lt;em&gt;is&lt;/em&gt; a Web Worker?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Web Workers&lt;/a&gt; are a simple means for web content to run scripts in background threads.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before I tell you &lt;em&gt;how&lt;/em&gt; to use a Web Worker, let's first go over things to consider before deciding to use a Web Worker.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You've &lt;a href="https://developer.chrome.com/docs/devtools/evaluate-performance/" rel="noopener noreferrer"&gt;profiled&lt;/a&gt; your website and discovered what you're considering putting in a Web Worker is actually slow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't bother with a Web Worker if you don't actually need it. Adding an extra thread to communicate with will complicate your code, and if you aren't getting noticeable performance gains from it, it's an unnecessary complication.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The work you want to offload &lt;em&gt;can&lt;/em&gt; be done asynchronously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is difficult to define, but &lt;a href="https://www.youtube.com/watch?v=L7-0ugujS2U" rel="noopener noreferrer"&gt;if you know, you know&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You don't need &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;document&lt;/code&gt;, or anything else DOM related.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Workers don't have direct access to the DOM of your site. There are ways around this but it's generally just a good idea &lt;em&gt;not&lt;/em&gt; to do it in the first place.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's something you've used a Web Worker for?
&lt;/h2&gt;

&lt;p&gt;See, I just love your questions!&lt;/p&gt;

&lt;p&gt;My app &lt;a href="https://lyricistant.app" rel="noopener noreferrer"&gt;Lyricistant&lt;/a&gt; currently has two web workers, but we're going to talk about the easier to understand one: &lt;a href="https://github.com/wardellbagby/lyricistant/blob/7e047c48a112076545551ee720bef01c2cef1f5c/packages/rhyme-generator/main/rhyme-generator.ts" rel="noopener noreferrer"&gt;a Web Worker that generates rhymes on demand, totally offline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I won't get into the nitty-gritty of how it all works, 'cause I mostly wrote it in a tired stupor at 2am one night, but it needs to load a 6 MiB JSON file, iterate through 135,165 words, and compare the syllables of those 135k words to the syllables of an inputted word.&lt;/p&gt;

&lt;p&gt;That is to say, if you type "Time" into Lyricistant (make sure to enable the Offline Rhymes in Preferences first!), my web worker will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the pronunciation of "time" (T AY1 M)&lt;/li&gt;
&lt;li&gt;Iterate over &lt;em&gt;every single one&lt;/em&gt; of the 135k words it has pronunciations for.&lt;/li&gt;
&lt;li&gt;Compare the syllables of "time" to the syllables of the word its currently looking at.&lt;/li&gt;
&lt;li&gt;Calculate a score based off the comparison in step 3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's also worth noting that this all happens on almost &lt;em&gt;every&lt;/em&gt; keypress (its debouncing, of course; I'm not a monster), so not only are we finding rhymes for "time", but also "tim" and "ti" if you type slow enough.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;very&lt;/strong&gt; naive way of generating rhymes, and also extremely slow. My excuse is I'm not a linguist! I'm barely a programmer! 😂&lt;/p&gt;

&lt;p&gt;Anyway, you can imagine how slow all of that code can be, so a Web Worker was the perfect choice to use! Although, fun fact: my initial implementation had this all run in the browser's main thread, and 60% of the time, it would finish in under 60 milliseconds, meaning Lyricistant could &lt;em&gt;mostly&lt;/em&gt; hit 60fps on my 2015 MacBook Pro using Chrome. Browsers are pretty fast!&lt;/p&gt;




&lt;h2&gt;
  
  
  Alright, enough talking. I wanna make my own Web Worker!
&lt;/h2&gt;

&lt;p&gt;Fair enough; this blog post was starting to look like one of those &lt;a href="https://www.reddit.com/r/mildlyinfuriating/comments/bcuvs3/literally_every_online_recipe_now_days/" rel="noopener noreferrer"&gt;online recipe intros&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating your Web Worker
&lt;/h3&gt;

&lt;p&gt;There are quite a few ways to make a Web Worker, but probably the easiest for most people will be using &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt; for bundling and &lt;a href="https://github.com/GoogleChromeLabs/comlink" rel="noopener noreferrer"&gt;Comlink&lt;/a&gt; for communication.&lt;/p&gt;

&lt;p&gt;Assuming you're already using Webpack, making your Web Worker is super easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myWorker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./path/to/my/file.js&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;my-worker-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bit you give to &lt;code&gt;URL&lt;/code&gt; should match exactly what it'd look like in an &lt;code&gt;import&lt;/code&gt; or &lt;code&gt;require&lt;/code&gt; statement. This works with any path mappings or resolve aliases you might have set up as well.&lt;/p&gt;

&lt;p&gt;I.e, if you'd normally import the file like &lt;code&gt;import '@my-app/my-file.ts'&lt;/code&gt;, then you'd do &lt;code&gt;new URL('@my-app/my-file.ts')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're &lt;em&gt;not&lt;/em&gt; using Webpack, you should probably consult your own module bundler's docs. If you're not using any bundler, omit the &lt;code&gt;new URL&lt;/code&gt; and instead do &lt;code&gt;new Worker("./path/to/your/output/file.js");&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Communicating with your Web Worker
&lt;/h3&gt;

&lt;p&gt;This is where Comlink comes into play!&lt;/p&gt;

&lt;p&gt;Web Workers, at their core, communicate via posting messages back and forth. Your main thread code communicates with your Worker with &lt;code&gt;worker.postMessage&lt;/code&gt;, your Worker listens to those messages with &lt;code&gt;self.onmessage&lt;/code&gt;, your Worker responds with &lt;code&gt;self.postMessage&lt;/code&gt;, and your main thread listens to those responses with &lt;code&gt;window.onmessage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's not very easy to follow, is it?&lt;/p&gt;

&lt;p&gt;Comlink removes all that hassle and instead gives you a much nicer &lt;code&gt;Promise&lt;/code&gt;-based API.&lt;/p&gt;

&lt;p&gt;Let's imagine you've got a Web Worker that simply multiples two numbers. With Comlink, you can set that up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Worker code math-worker.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expose&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;comlink&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;multiply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiplicand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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="nx"&gt;multiplicand&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Every function we "expose" this way will be available in the main thread. Functions that aren't exposed won't be available.&lt;/span&gt;
&lt;span class="nf"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Main thread code&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;wrap&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;comlink&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;mathWorker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./math-worker.ts&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;math-worker&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;math&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mathWorker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Wrapping mathWorker gives us access to the exposed functions, but now they return Promises!&lt;/span&gt;
&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Know anything I should be on the lookout for? Or just general advice?
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, your Worker doesn't have access to the DOM or &lt;code&gt;window&lt;/code&gt;. That makes sense; you don't actually have a DOM in a Web Worker because you don't have any UI. Outside of that, you can do almost anything you want, including spawning &lt;em&gt;more&lt;/em&gt; Workers!&lt;/p&gt;

&lt;p&gt;You also can't use &lt;code&gt;this&lt;/code&gt; at the root level of your Worker code; use &lt;code&gt;self&lt;/code&gt; instead. &lt;code&gt;this&lt;/code&gt; still works fine in functions and classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify that your code is actually running in a Web Worker!&lt;/strong&gt; This bit me a few times, where I had messed up the setup and had inadvertently ran my worker in the main thread. Super easy to do if you import the file your Worker is supposed to run directly as an &lt;code&gt;import&lt;/code&gt;. The easiest way to verify your Web Worker is running is by opening up Dev Tools and going to the "Sources" tab. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpm9ey63obj2wzbatc219.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpm9ey63obj2wzbatc219.png" alt="Image of Chrome Dev Tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In that image, "top" refers to the main thread, and "platform" and "rhyme-generator" are Web Workers.&lt;/p&gt;

&lt;p&gt;If you only see "top", your Web Worker isn't running. This is either because you haven't started it yet (which you do by sending some data to it) or because you've misconfigured it.&lt;/p&gt;

&lt;p&gt;Also, remember that concurrency is difficult! Try and keep your Workers as simple and stateless as possible. This'll make your life much easier overall.&lt;/p&gt;

&lt;p&gt;One last tip: much like regular threads, there's diminishing returns to having too many Web Workers. A tip that I've heard is the maximum number of Web Workers you should spawn is &lt;code&gt;window.navigator.hardwareConcurrency - 1&lt;/code&gt;. We subtract one to save a core for the main thread. &lt;/p&gt;




&lt;h2&gt;
  
  
  Alright, I think I've got it now! Thanks!
&lt;/h2&gt;

&lt;p&gt;Of course, no problem! Have fun and don't work your workers too hard!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The architecture of an Electron app ported to Web</title>
      <dc:creator>Wardell Bagby</dc:creator>
      <pubDate>Mon, 10 Jan 2022 01:27:16 +0000</pubDate>
      <link>https://dev.to/wardellbagby/the-architecture-of-an-electron-app-ported-to-web-399e</link>
      <guid>https://dev.to/wardellbagby/the-architecture-of-an-electron-app-ported-to-web-399e</guid>
      <description>&lt;p&gt;Hey, y'all! I'm Wardell! In my free time (and when I &lt;em&gt;should&lt;/em&gt; be sleeping), I hack on my app, &lt;a href="https://lyricistant.app" rel="noopener noreferrer"&gt;Lyricistant&lt;/a&gt;. Buckle up, 'cause this is going to be a long one.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;em&gt;So, what is Lyricisant?&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Lyricistant, at face value, is a very simple app. Its own tag line is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lyricistant is a writing app geared toward helping you write lyrics, poetry, or anything else you desire!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I made Lyricistant to help myself write lyrics for my own music, but in true developer fashion, I've kept "just one more feature"-ing it until it ballooned into something much more than what I set out to make.&lt;/p&gt;

&lt;p&gt;Going to its &lt;a href="https://lyricistant.app" rel="noopener noreferrer"&gt;website&lt;/a&gt;, most of what you see is a big text area, prompting you to type out your lyrics. Typing in some text will show a list of rhymes related to the word near your cursor.&lt;/p&gt;

&lt;p&gt;Pretty simple, right? And yet, it'll be reaching its 3rd year of active development in February of 2022. It's also on track to easily surpass 500 commits in 2022. How does something with such a simple purpose still not end up finished after all this time?&lt;/p&gt;

&lt;p&gt;To be fair, the length of time a project has been alive and its number of commits isn't a fair measure of the complexity of the project, but &lt;em&gt;still.&lt;/em&gt; It's a glorified &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; with a little bit of Javascript to tie it all together! It's a bit silly, isn't it?&lt;/p&gt;

&lt;p&gt;There are a &lt;strong&gt;lot&lt;/strong&gt; of reasons Lyricistant is so complex, but today, we're going to talk about one big one: Lyricistant, which started as an Electron app, now builds both a fully usable offline Electron app &lt;em&gt;and&lt;/em&gt; a website from the same codebase. Plus a couple other apps we aren't going to talk about today. The coolest part, though? &lt;/p&gt;

&lt;p&gt;The UI code has absolutely no idea... 🤫&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;em&gt;Electron is just Chrome + Node. What's the big deal?&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;I thought the same thing when I first chose Electron for Lyricistant. I'd just write my UI code like how I would for a website, but with the benefit of being able to use Node whenever I want!&lt;/p&gt;

&lt;p&gt;That Node pitfall is one of ways that Electron traps ya, though!&lt;/p&gt;

&lt;p&gt;Once you start relying on Node, it's definitely not easy to stop. This is doubly true for an app that primarily deals with reading and writing files; there's no easy translation from &lt;code&gt;fs.readFile&lt;/code&gt; to &lt;code&gt;&amp;lt;input type="file"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Outside of Node, there's another big gotcha: Electron has &lt;strong&gt;two&lt;/strong&gt; processes working in tandem, and you communicate between them using only data that is supported by the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm" rel="noopener noreferrer"&gt;structured clone algorithm&lt;/a&gt;. You can send primitive data, object literals, arrays, and...not much else.&lt;/p&gt;

&lt;p&gt;The main process lets you do all your fancy Node stuff; the renderer process runs your UI code.&lt;/p&gt;

&lt;p&gt;Communication in a normal Electron app looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in Electron's main process&lt;/span&gt;
&lt;span class="nx"&gt;browserWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webContents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-from-main&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;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;ipcMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-from-renderer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kendrick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// logs 'here'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in Electron's renderer process (i.e., a BrowserWindow)&lt;/span&gt;
&lt;span class="nx"&gt;ipcRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-from-main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// logs 'world'&lt;/span&gt;
  &lt;span class="nx"&gt;ipcRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-from-renderer&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;kendrick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;here&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;Imagine you've got a codebase with a ton of those listeners thrown around, and you'll be able to feel the pain I went through when I decided to port Lyricistant to the web.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;em&gt;Alright, I think I'm understanding your struggles. How'd you get rid of all those listeners?&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Well....I didn't!&lt;/p&gt;

&lt;p&gt;Electron's inter-process communication (aka IPC) is only bad &lt;em&gt;because&lt;/em&gt; it's coupled so closely to Electron. The UI code has to know that it's running on Electron so that it knows how to communicate with the main process.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But&lt;/em&gt;, there's nothing stopping us from wrapping Electron's IPC with our own, Electron-agnostic logic. Which is exactly what I did.&lt;/p&gt;

&lt;p&gt;Enter the &lt;code&gt;Delegate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Delegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When running on Electron, we provide the UI code with a &lt;code&gt;Delegate&lt;/code&gt; that delegates to Electron's &lt;code&gt;ipcMain&lt;/code&gt; to communicate between the two processes. Lyricistant calls this object a Platform Delegate. &lt;/p&gt;

&lt;p&gt;We also provide the code running in Electron's main process a similar object called the Renderer Delegate. When the UI uses the Platform Delegate to send data, main process code gets that data via its Renderer Delegate. The main process code can also use the Renderer Delegate to send data to the UI.&lt;/p&gt;

&lt;p&gt;When running on Web, though...&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;em&gt;Wait wait wait. Yes, you can wrap Electron's IPC but browsers still only give you one process... You've got nothing to send data to.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Web Workers&lt;/a&gt; would like to talk to you, but that's actually not what I did initially.&lt;/p&gt;

&lt;p&gt;Even with a single process, we can &lt;em&gt;emulate&lt;/em&gt; Electron's two process model by just calling listeners ourselves!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebPlatformDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getListeners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// return the listeners set on `on`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rendererDelegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getListeners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebRendererDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getListeners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// return the listeners set on `on`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;platformDelegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getListeners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&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;h3&gt;
  
  
  Okay, you've successfully emulated Electron's two processes in a single process. But how do you solve the no-Node problem?
&lt;/h3&gt;

&lt;p&gt;Let's talk about inversion of control. If you're an Android developer like me, think dependency injection, like Dagger. If you're a React developer, think of &lt;code&gt;useContext&lt;/code&gt;. If you're a Node developer, think of the service locator pattern.&lt;/p&gt;

&lt;p&gt;Regardless of the pattern, the idea is the same; we decouple the platform-specific code from the platform-agnostic code.&lt;/p&gt;

&lt;p&gt;Let's use saving a new file as an example. When the user wants to save a new file, Lyricistant needs to do quite a few things!&lt;/p&gt;

&lt;p&gt;When running on Electron: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the current text that's displayed on the UI.&lt;/li&gt;
&lt;li&gt;Show a dialog to let the user pick where to save the file.&lt;/li&gt;
&lt;li&gt;Save the data to the file the user picked.&lt;/li&gt;
&lt;li&gt;Store the path to the file so that the user won't need to pick it again if they hit save again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When running on Web:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the current text that's displayed on the UI.&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag with a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL" rel="noopener noreferrer"&gt;URL to a blob of the current text as its href.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Programmatically click the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;Let the browser save the file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1 is platform-agnostic. It doesn't matter if it's the Electron version or the Web version; the logic for that step is exactly the same. The rest of the steps are platform-specific.&lt;/p&gt;

&lt;p&gt;We can use inversion of control to decouple those steps away from the "common" steps. Lyricistant forces its platforms to provide implementations for steps that are platform-specific. For file saving, it looks something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FileMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * A unique way of identifying a file, dependent on the platform in question. For instance, against a Desktop
   * platform, we would expect this to be a file path, such as "/Desktop/myfile.txt". On Android, we might expect this
   * to be a content URI, such as "content://documents/1".
   */&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * A human displayable name that refers to this file in question. If the path can double as a human displayable name,
   * this can be omitted.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Files&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;saveFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FileMetadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using the &lt;code&gt;Files&lt;/code&gt; interface, once Lyricistant has retrieved the text from the UI, it will send that text as an &lt;code&gt;ArrayBuffer&lt;/code&gt; to the implementation of &lt;code&gt;Files&lt;/code&gt; it had injected, along with any &lt;code&gt;path&lt;/code&gt; it &lt;em&gt;might&lt;/em&gt; have (remember Electron's step 4?) so that the &lt;code&gt;Files&lt;/code&gt; implementation can do the work of saving the file.&lt;/p&gt;

&lt;p&gt;By following this pattern, we can do all sorts of stuff without ever knowing for sure what platform we're running on!&lt;/p&gt;


&lt;h3&gt;
  
  
  Hmm. Okay, but what about those common steps?
&lt;/h3&gt;

&lt;p&gt;Good point. &lt;/p&gt;

&lt;p&gt;We &lt;em&gt;also&lt;/em&gt; use inversion of control to handle that! Those common steps can be shared across multiple platforms, but the platforms themselves don't really &lt;em&gt;need&lt;/em&gt; to know what's handling those steps. &lt;/p&gt;

&lt;p&gt;Enter the idea of a &lt;code&gt;Manager&lt;/code&gt;. Simple name, I know. Simple interface, too.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Manager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The idea is that we group common functionality into a &lt;code&gt;Manager&lt;/code&gt;, and that manager will register listeners on the Renderer Delegate, inject various platform-specific classes to handle platform-specific logic, and send data back to the UI via the Renderer Delegate.&lt;/p&gt;

&lt;p&gt;Lyricistant has a few &lt;code&gt;Managers&lt;/code&gt;: &lt;code&gt;FileManager&lt;/code&gt;, and &lt;code&gt;PreferencesManager&lt;/code&gt; to name a couple. Those are core to Lyricistant functionality; you always need to open a file and manage your preferences, no matter the platform.&lt;/p&gt;

&lt;p&gt;All platforms need to do is set the list of managers they need and then call &lt;code&gt;register&lt;/code&gt; on those managers when the platform starts up. They get all the common code for free. No need to worry about storing the current file, or loading any data the user might not have saved the last time they closed Lyricistant.&lt;/p&gt;

&lt;p&gt;However, there's some functionality that only matters to certain platforms. For instance, there's a &lt;code&gt;QuitManager&lt;/code&gt; that only Electron uses to manage prompting the user when they attempt to quit the app. It's allowed to use Electron-specific functionality because it's not stored with the rest of the common code. Electron adds the &lt;code&gt;QuitManager&lt;/code&gt; to its list of managers, it gets registered with its manager friends, and gets to do its job only for Electron. Web has no idea it exists.&lt;/p&gt;


&lt;h3&gt;
  
  
  Can I get a tl;dr? That was a lot!
&lt;/h3&gt;

&lt;p&gt;Sure!&lt;/p&gt;

&lt;p&gt;There's 3 "types" of code in Lyricistant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI code (e.g., the React code that renders the UI)&lt;/li&gt;
&lt;li&gt;Platform-specific code (e.g., Electron code that uses Node to save a file, like &lt;code&gt;Files&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Platform-agnostic code (e.g., code that handles coordinating between the UI and the platform-specific code, like the &lt;code&gt;Manager&lt;/code&gt;s).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Electron, platform-specific code and platform-agnostic code all run in the main process. UI code runs in the renderer process.&lt;/p&gt;

&lt;p&gt;In Web, all code runs in the same process. (At least for now! Be on the lookout for the next Lyricistant release!)&lt;/p&gt;


&lt;h3&gt;
  
  
  Wow, that's a lot of architecture just to build an Electron app and a website from the same codebase.
&lt;/h3&gt;

&lt;p&gt;You're telling me! It's not perfect by any means, and this isn't even how it is &lt;em&gt;right now&lt;/em&gt;; there's a lot of tweaks to this in the repo that haven't been pushed to a new release yet, but the ideas are still very much the same.&lt;/p&gt;

&lt;p&gt;I don't think &lt;em&gt;everyone&lt;/em&gt; would need to do something as heavy as this; a lot of apps will probably work just fine by doing platform checks in their UI code. Even more would be fine by just solely using browser APIs and not relying on Node at all. Lyricistant is a bit special, mostly because I'm the one who writes it and I like to be more complicated than necessary. 🙃&lt;/p&gt;

&lt;p&gt;Even factoring that in, I hope that some of this was interesting, or at least a little helpful to those of you who might be considering creating an Electron app!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/wardellbagby" rel="noopener noreferrer"&gt;
        wardellbagby
      &lt;/a&gt; / &lt;a href="https://github.com/wardellbagby/lyricistant" rel="noopener noreferrer"&gt;
        lyricistant
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A helpful writing assistant for lyricists!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>electron</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
