<?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>Forem: Oranda IO</title>
    <description>The latest articles on Forem by Oranda IO (@orandaio).</description>
    <link>https://forem.com/orandaio</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%2F567802%2Fa45c62cf-3f81-452e-ac8c-fa55a2b422a8.jpeg</url>
      <title>Forem: Oranda IO</title>
      <link>https://forem.com/orandaio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/orandaio"/>
    <language>en</language>
    <item>
      <title>I wanted a simpler way to run commands in a container from code, so l built a small HTTPS alternative to SSH</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Wed, 25 Feb 2026 16:23:38 +0000</pubDate>
      <link>https://forem.com/orandaio/i-wanted-a-simpler-way-to-run-commands-in-a-container-from-code-so-l-built-a-small-https-n92</link>
      <guid>https://forem.com/orandaio/i-wanted-a-simpler-way-to-run-commands-in-a-container-from-code-so-l-built-a-small-https-n92</guid>
      <description>&lt;p&gt;Github Repo:  &lt;a href="https://github.com/Oranda-IO/Shed" rel="noopener noreferrer"&gt;https://github.com/Oranda-IO/Shed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm building a mobile IDE that needs to remotely execute commands on a Docker container from code.&lt;/p&gt;

&lt;p&gt;I made the switch to containerized development, but CDE tools (like Codespaces) don't always provide Docker host access, so Docker Exec is not an option.&lt;/p&gt;

&lt;p&gt;SSH is an option..  but managing SSH connections for one-off commands is costly, and I found the developer experience frustrating (e.g. need an SSH client lib, need to manage connection state). It felt especially counter-intuitive in a Lambda.&lt;/p&gt;

&lt;p&gt;So (possibly against my better judgment) I built a small Go daemon (~9MB built) I'm calling "SHED" for Secure HTTP Execution Daemon.&lt;/p&gt;

&lt;p&gt;You can drop it into a container and run remote kernel commands with stateless HTTPS fetch calls instead of opening a stateful SSH terminal session.&lt;/p&gt;

&lt;p&gt;Shed exposes an /exec endpoint over HTTPS with bearer token auth. You POST a JSON command, and you get JSON back with stdout, stderr, and the exit code.&lt;/p&gt;

&lt;p&gt;I know I'm reinventing a wheel. SSH is reliable and trustworthy, but it does come with baggage that adds resistance for the modern web.&lt;/p&gt;

&lt;p&gt;I did my best to defend it technically in the motivation section here: &lt;a href="https://github.com/Oranda-IO/Shed#motivation" rel="noopener noreferrer"&gt;https://github.com/Oranda-IO/Shed#motivation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is early and experimental, but I wanted to share in case anyone else has this problem or finds this approach useful.&lt;/p&gt;

&lt;p&gt;All feedback is appreciated&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>docker</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an AI Voice Assistant in 1 Minute (Command Line)</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Sun, 08 Jun 2025 17:03:44 +0000</pubDate>
      <link>https://forem.com/orandaio/building-an-ai-voice-assistant-in-1-minute-command-line-n4i</link>
      <guid>https://forem.com/orandaio/building-an-ai-voice-assistant-in-1-minute-command-line-n4i</guid>
      <description>&lt;p&gt;Today, I decided to build an AI Voice Assistant.  &lt;/p&gt;

&lt;p&gt;My goal was to convert my voice to text, pass it through an LLM, and stream it back as audio - all within a few seconds in MacOS Terminal.&lt;/p&gt;

&lt;p&gt;I was able to accomplish this quickly with help from GPT-4o.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;p&gt;We'll build this using 3 OpenAI models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Whisper&lt;/strong&gt;:  Speech -&amp;gt; Text&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;GPT&lt;/strong&gt;:  LLM to Process Text&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;TTS&lt;/strong&gt;:  Text -&amp;gt; Speech&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't already have API keys, you can get them here:  &lt;a href="https://openai.com/api" rel="noopener noreferrer"&gt;https://openai.com/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before starting, you'll need to export your OpenAI API Key for the commands to work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export OPENAI_API_KEY=sk-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't want to use OpenAI models, there are plenty of alternatives (Open-Whisper, LM Studio, Piper, Claude, etc...).&lt;/p&gt;

&lt;h1&gt;
  
  
  The Minute
&lt;/h1&gt;

&lt;p&gt;Over the next minute, you can paste these commands into your MacOS Terminal:&lt;/p&gt;

&lt;h2&gt;
  
  
  Record Your Request
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sox -d -q test.wav trim 0 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the SoX tool (Sound eXchange) for recording / processing audio.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; The &lt;code&gt;-d&lt;/code&gt; option says to use the input device.&lt;/li&gt;
&lt;li&gt; The &lt;code&gt;-q&lt;/code&gt; option enables quiet mode (to suppress output).
&lt;/li&gt;
&lt;li&gt; The recording is saved as &lt;code&gt;test.wav&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;trim 0 3&lt;/code&gt; tells sox to listen for 3 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Convert to Text
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRANSCRIPTION=$(curl -s -X POST https://api.openai.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: multipart/form-data" \
  -F file=@test.wav \
  -F model=whisper-1 \
  | jq -r .text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run OpenAI's Whisper model to convert your audio into text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process the Text
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPLY=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"gpt-3.5-turbo\",
    \"messages\": [
      { \"role\": \"system\", \"content\": \"You are a helpful assistant. Keep responses short.\" },
      { \"role\": \"user\", \"content\": \"$TRANSCRIPTION\" }
    ]
  }" | jq -r .choices[0].message.content)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses GPT-3.5 to process your request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream the Reply
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -s -X POST https://api.openai.com/v1/audio/speech \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"tts-1\",
    \"input\": \"$REPLY\",
    \"voice\": \"fable\",
    \"response_format\": \"pcm\",
    \"sample_rate\": 24000
  }" | sox -t raw -b16 -e signed-integer -r24000 -c1 -L - -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses OpenAI's TTS API to convert the output of GPT back into speech.  It then streams that to &lt;code&gt;sox&lt;/code&gt; in lightweight PCM format.&lt;/p&gt;

&lt;h1&gt;
  
  
  Done!
&lt;/h1&gt;

&lt;p&gt;You can add all of this to a single shell script to make it easier to run:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;assist.sh&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Record WAV — fixed 3 second clip&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🎙️  Recording 3 second clip..."&lt;/span&gt;
sox &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; test.wav trim 0 3

&lt;span class="c"&gt;# Transcribe with Whisper&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"📝 Transcribing..."&lt;/span&gt;
&lt;span class="nv"&gt;TRANSCRIPTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.openai.com/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: multipart/form-data"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@test.wav &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whisper-1 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Print what was transcribed&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🗣️  You said: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$TRANSCRIPTION&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Chat with GPT&lt;/span&gt;
&lt;span class="nv"&gt;REPLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.openai.com/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;messages&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [
      { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;You are a helpful assistant. Keep responses short.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; },
      { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$TRANSCRIPTION&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; }
    ]
  }"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .choices[0].message.content&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Print reply&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🤖 AI reply: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# TTS — stream back and play&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🔊 Speaking reply..."&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.openai.com/v1/audio/speech &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPENAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tts-1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$REPLY&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;voice&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fable&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;response_format&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;pcm&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sample_rate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 24000
  }"&lt;/span&gt; | sox &lt;span class="nt"&gt;-t&lt;/span&gt; raw &lt;span class="nt"&gt;-b16&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; signed-integer &lt;span class="nt"&gt;-r24000&lt;/span&gt; &lt;span class="nt"&gt;-c1&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; - &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;

&lt;span class="c"&gt;# Final message&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Done."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, to run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x ./assist.sh
./assist.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This is a quick AI assistant you can use by typing "assist" in the command line.  &lt;/p&gt;

&lt;p&gt;You can extend yours to use "silence" to listen until you stop speaking or listen on a loop for a hot-key, etc.&lt;/p&gt;

&lt;p&gt;I've extended mine to run within an express server for better control and both input / output streaming for embedded devices.&lt;/p&gt;

&lt;p&gt;Let me know if you have any questions!  &lt;/p&gt;

&lt;p&gt;Happy Hacking,&lt;br&gt;
Zuluana&lt;/p&gt;

</description>
      <category>openai</category>
      <category>cli</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Home Screen Shortcuts in React Native (with Expo)</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Thu, 19 Aug 2021 20:25:06 +0000</pubDate>
      <link>https://forem.com/orandaio/home-screen-shortcuts-in-react-native-with-expo-a5b</link>
      <guid>https://forem.com/orandaio/home-screen-shortcuts-in-react-native-with-expo-a5b</guid>
      <description>&lt;p&gt;On March 22, 2017 Apple acquired "Workflow", an app which has since been re-branded and distributed as "Shortcuts".&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%2Fo26suilc08t3l77r9q6m.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%2Fo26suilc08t3l77r9q6m.png" alt="Workflow" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a versatile app, enabling users to string together features from various apps installed on their device.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One such feature is the ability to add Shortcuts to the iOS home-screen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Developers can access these features programmatically with &lt;a href="https://developer.apple.com/documentation/sirikit" rel="noopener noreferrer"&gt;SiriKit&lt;/a&gt;, the SDK used to interact with Siri and Shortcuts.&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%2Fngjox16nsgb80kupxnan.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%2Fngjox16nsgb80kupxnan.png" alt="siri copy" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use SiriKit in React Native, like most native SDKs, it's common to install an existing library and link native dependencies.&lt;/p&gt;

&lt;p&gt;At this time, I've only found one popular library to solve this problem:  &lt;a href="https://github.com/Gustash/react-native-siri-shortcut" rel="noopener noreferrer"&gt;react-native-siri-shortcut&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I found another called &lt;a href="https://www.npmjs.com/package/react-native-siri-shortcuts" rel="noopener noreferrer"&gt;react-native-siri-shortcuts&lt;/a&gt;, but it has low NPM activity and partial implementation.&lt;/p&gt;

&lt;p&gt;Unfortunately, I found three issues with this approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Platform Compatibility
&lt;/h3&gt;

&lt;p&gt;Because SiriKit is an iOS-only feature, you won't be able to save shortcuts to your Android home-screen.&lt;/p&gt;

&lt;p&gt;Android &lt;a href="https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts" rel="noopener noreferrer"&gt;has its own solution&lt;/a&gt; for this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expo Ejection
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/Gustash/react-native-siri-shortcut" rel="noopener noreferrer"&gt;react-native-siri-shortcut&lt;/a&gt; needs to be linked, so, to use it with an Expo app, you'll need to eject.&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%2Fsdqth3wh9nya6f3zt772.gif" 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%2Fsdqth3wh9nya6f3zt772.gif" alt="c6d1d5e3eac6bd923ec571c3eedeaa1e" width="299" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, with the new "Bare" Expo workflow, this is &lt;a href="https://docs.expo.dev/bare/exploring-bare-workflow/" rel="noopener noreferrer"&gt;easier than ever&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viral License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/Gustash/react-native-siri-shortcut" rel="noopener noreferrer"&gt;react-native-siri-shortcut&lt;/a&gt; is licensed under &lt;a href="https://github.com/Gustash/react-native-siri-shortcut/blob/main/LICENSE" rel="noopener noreferrer"&gt;GPL-3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is considered a "copyleft" and "viral" license, because all distributed dependent work is (usually) required to be licensed under the same terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. - &lt;a href="https://www.gnu.org/licenses/gpl-3.0.en.html" rel="noopener noreferrer"&gt;GPL v3 Section 5c&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I personally avoid dependencies with viral licensing, especially in commercial products.&lt;/p&gt;

&lt;p&gt;If you don't know what licenses you're currently using, I suggest the &lt;a href="https://www.npmjs.com/package/license-checker" rel="noopener noreferrer"&gt;license-checker&lt;/a&gt; NPM tool.&lt;/p&gt;

&lt;p&gt;I use this command to check for &lt;a href="https://tldrlegal.com/licenses/tags/Viral" rel="noopener noreferrer"&gt;several common "viral" licenses&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;license-checker | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"GPL&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;CC&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;MTS&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;Mechanical"&lt;/span&gt; &lt;span class="nt"&gt;-B1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip:  Not all CC licenses are copyleft.  Look for the "ShareAlike" qualifier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Homeward
&lt;/h2&gt;

&lt;p&gt;Given these problems, we built a small web app, called &lt;a href="https://github.com/CodalReef/homeward" rel="noopener noreferrer"&gt;Homeward&lt;/a&gt;, to save shortcuts to the iOS / Android home-screen.&lt;/p&gt;

&lt;p&gt;To use it, re-direct users from your mobile app to the Homeward web app with the required parameters.&lt;/p&gt;

&lt;p&gt;The user will then be prompted to save the link to their home-screen:&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%2Fanu7aab9g5bwmlnr2nl3.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%2Fanu7aab9g5bwmlnr2nl3.png" alt="Example" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To simplify this process in React Native / Expo apps, we built &lt;a href="https://github.com/CodalReef/homeward-sdk" rel="noopener noreferrer"&gt;Homeward SDK&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i https://github.com/CodalReef/homeward-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;&lt;br&gt;
 We plan on publishing to npm shortly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create the Link
&lt;/h3&gt;

&lt;p&gt;Next, create the deep link you'd like saved to the user's home-screen:&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;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myapp://feature1.context1?payload={ ... }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact format of the link will depend on your application.&lt;/p&gt;

&lt;p&gt;If you're using Expo, the &lt;a href="https://docs.expo.dev/guides/linking/" rel="noopener noreferrer"&gt;expo-linking&lt;/a&gt; library can help with 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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Linking&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-linking&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;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Linking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeUrl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build a Web App Manifest
&lt;/h3&gt;

&lt;p&gt;The Homeward SDK directs the user to a web browser where they can save the icon to their home-screen.&lt;/p&gt;

&lt;p&gt;The style of both the icon and the web page are controlled by the supplied &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest" rel="noopener noreferrer"&gt;Web App Manifest&lt;/a&gt; JSON.&lt;/p&gt;

&lt;p&gt;Web App Manifests are an experimental web standard supported by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest#browser_compatibility" rel="noopener noreferrer"&gt;several major browsers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's make a Web App Manifest to save a Calendar icon to the home-screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebAppManifest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;homeward&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;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebAppManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Calendar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;background_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#79ccd2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;theme_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#79ccd2&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;icons&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;src&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;https://image.flaticon.com/icons/png/512/2948/2948115.png&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;sizes&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;512x512&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;type&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;image/jpeg&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;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest/name" rel="noopener noreferrer"&gt;name&lt;/a&gt; field will be shown below the icon on the home-screen.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color" rel="noopener noreferrer"&gt;theme_color&lt;/a&gt; field is used on Android to style the navigation bar and on iOS to style the default icon.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest/background_color" rel="noopener noreferrer"&gt;background_color&lt;/a&gt; field is a transitory color shown while loading the browser.&lt;/p&gt;

&lt;p&gt;If you do not specify an icon, &lt;a href="https://github.com/CodalReef/homeward#default-icon" rel="noopener noreferrer"&gt;a default icon is created&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; &lt;br&gt;
Android has full support for custom icons, but on iOS you &lt;a href="https://github.com/CodalReef/homeward#custom-icon" rel="noopener noreferrer"&gt;must specify a "180x180" icon&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Trigger Save to Home
&lt;/h3&gt;

&lt;p&gt;With the link and the manifest, we can now save the icon to the home-screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveToHome&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;homeward&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;saveToHome&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately redirects the user to the web app with instructions on how to save the shortcut (shown above).&lt;/p&gt;

&lt;p&gt;Once saved, the user can tap the home-screen icon to be directed to the cached Homeward PWA.  This then immediately opens the provided link.&lt;/p&gt;

&lt;p&gt;The PWA stays open in the switcher and can be tapped again to re-open the deep link.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See Pitch / Antipitch and Disclaimer for caveats and further discussion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;p&gt;I originally considered abstracting over native iOS / Android APIs, but, a solution effectively exists.&lt;/p&gt;

&lt;p&gt;On both plaftorms, the built-in web browser has a "Save to Home-Screen" feature.&lt;/p&gt;

&lt;p&gt;Apps like "Facebook Groups", "Workflow", and others have taken advantage of this to create ad-hoc home-screen shortcuts.&lt;/p&gt;

&lt;p&gt;This approach is &lt;a href="https://stackoverflow.com/questions/28042152/link-to-safari-add-to-home-screen-from-inside-app" rel="noopener noreferrer"&gt;documented on StackOverflow&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Redirect from the Native App to a Web App&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the timestamp of the request.  If it's new, prompt the user to save the shortcut to their home-screen.  If it's old, re-direct to the deep link.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the user taps the icon on their home-screen, it will redirect to the same Web URL but with an old timestamp, triggering the deep link.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This solution provides a standardized way of saving a deep link across platforms without abstracting over disparate APIs.&lt;/p&gt;

&lt;p&gt;There's a &lt;em&gt;lot&lt;/em&gt; of interesting work to be done bridging the gap in SiriKit, Shortcuts, and the associated Android SDKs.  &lt;/p&gt;

&lt;p&gt;I'm sure usable SDK wrappers will be available soon, but in the meantime, this browser-based solution has worked for me.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this write-up on SiriKit, shortcuts, and my personal struggles.&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
CodalReef&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you'd like more articles like this, feel free to follow me on:  &lt;a href="https://github.com/CodalReef" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://dev.to/codalreef"&gt;Dev&lt;/a&gt;, &lt;a href="https://twitter.com/CodalReef" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/CodalReef" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Calendar icon was created by &lt;a href="https://www.flaticon.com/authors/bqlqn" rel="noopener noreferrer"&gt;bqlqn&lt;/a&gt; and hosted by &lt;a href="https://www.flaticon.com/free-icon/calendar_2948115?term=calendar&amp;amp;page=2&amp;amp;position=69&amp;amp;page=2&amp;amp;position=69&amp;amp;related_id=2948115" rel="noopener noreferrer"&gt;Flat Icon&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>javascript</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>The Plugin Market: An Open Letter to Apple</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Sun, 07 Feb 2021 17:29:58 +0000</pubDate>
      <link>https://forem.com/orandaio/extensible-apps-an-open-note-to-apple-99l</link>
      <guid>https://forem.com/orandaio/extensible-apps-an-open-note-to-apple-99l</guid>
      <description>&lt;p&gt;Hi Apple,&lt;/p&gt;

&lt;p&gt;Today I’d like to propose a change to your revenue model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a "Plugin Market" to sell external app extensions (also called Plugins).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Developers should be able to build "Extensible" apps and capitalize by selling "Plugins" (like Wordpress).&lt;/p&gt;

&lt;p&gt;These "Plugins" should be able to dynamically (and drastically) change existing functionality.&lt;/p&gt;

&lt;p&gt;This has been possible on the web for ages, and I feel it’s time to fully support Plugin Oriented Design (POD) on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine I build an "extensible" native app and sell "Plugins" using an in-app store.  I take a cut of the profits, and so do the Plugin developers.&lt;/p&gt;

&lt;p&gt;Today, I'd expect this app to be rejected as per sec. 3.3.2 of the Apple Developer Agreement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Except as set forth in the next paragraph, an Application may not download or install executable code. Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS. - &lt;a href="https://developer.apple.com/terms/" rel="noopener noreferrer"&gt;https://developer.apple.com/terms/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’d certainly be in violation of (b), possibly (a), and perhaps (c), depending on the implementation.  Let’s focus on (a) and (b):&lt;/p&gt;

&lt;p&gt;I'm sure you (Apple) like getting paid for "Apps" in your store. You take a cut of the sale, and it’s a big part of your revenue.  So, if everyone started selling there own "Plugins", you'd lose money.&lt;/p&gt;

&lt;p&gt;All those “Plugins” might have otherwise been registered as new “Apps”.  Each of which comes with a set of developers paying for Apple Developer accounts.  All that revenue would be re-directed to the App / Plugin developers.. which you (understandably) want a cut of.&lt;/p&gt;

&lt;p&gt;So... as much as you want to allow innovation and extension on your platform, you're concerned about losing control and money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Solution
&lt;/h2&gt;

&lt;p&gt;I could build a web-app... but I won't get your nice native features.&lt;/p&gt;

&lt;p&gt;I understand you're getting pressure from competitors to close the gap between web and native, but that’s a problem for another day.&lt;/p&gt;

&lt;p&gt;For now, let's say I &lt;em&gt;really&lt;/em&gt; want the newest native features &lt;em&gt;and&lt;/em&gt; my app in your "App Store".&lt;/p&gt;

&lt;p&gt;We could keep the iOS App clean of any store-like interface and have users buy / register plugins on the web, but I'm not sure you'd like that...  I wouldn't be surprised if my app was rejected or removed.&lt;/p&gt;

&lt;p&gt;So, how can we make this work for everyone?&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed Solution
&lt;/h2&gt;

&lt;p&gt;You already have first-class support for &lt;a href="https://developer.apple.com/app-extensions/" rel="noopener noreferrer"&gt;OS extensions&lt;/a&gt;, but not &lt;a href="https://developer.apple.com/forums/thread/68972" rel="noopener noreferrer"&gt;iOS App Extensions&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Introduce first-class support for "iOS App Extensions" (Plugins).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are the benefits (edited after comments):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Profit&lt;/strong&gt;:  This could be an untapped greenfield opportunity for mobile.  Nested Plugins produce a natural, exponential fanout of charge points.  Coupled with technical advantage and demand, this should &lt;em&gt;increase&lt;/em&gt; revenue.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Momentum&lt;/strong&gt;:  People are &lt;em&gt;doing this anyways&lt;/em&gt; (think Expo).  It only benefits you to capture this revenue instead of fighting it.  It takes time and money to filter apps that break these terms.  If you adapt these concepts, those resources can be repurposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Employment&lt;/strong&gt;:  Look at all the &lt;em&gt;jobs&lt;/em&gt; created from Wordpress alone.  Now imagine extensibility as a commonplace feature of mobile / web systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competition&lt;/strong&gt;:  Your competitors are already adapting.  Google Play &lt;a href="https://developer.android.com/guide/app-bundle/play-feature-delivery" rel="noopener noreferrer"&gt;supports dynamic feature delivery&lt;/a&gt;.  I don't yet believe they support external developer injections or nested features.  There's also dynamic module loading on the web.  Plus, you're getting pressure from other players (like Google) to continue bridging native features (and vice versa).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Innovation&lt;/strong&gt;:  The current agreement is technically limiting.  By removing (or lessening) these restrictions, developers have more freedom to innovate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low Cost&lt;/strong&gt;:  It should be possible to leave the existing deployment framework and retrofit support for Plugins.  This could be an optional feature devs use.  It's a low cost, high reward feature for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarification&lt;/strong&gt;:  The current agreement leaves room for interpretation.  Specifically part (a). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt;:  This gives you touch-points &lt;a href="https://www.businessinsider.com/apple-refuses-to-allow-major-apps-from-microsoft-google-facebook-2020-8" rel="noopener noreferrer"&gt;to assert control&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;:  By limiting the set of APIs available to Plugins, they have a subset of the security profile of the base application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Demand for extensive dynamic code interpretation is increasing.  It may just be a matter of time before it's more advantageous to accept it than to fight it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Registration
&lt;/h4&gt;

&lt;p&gt;Allow developers to register their Apps / Plugins as “Extensible”.  Plugins should be able to extend both Apps and other Plugins.  Have developers express which Apps / Plugins their Plugins can be installed into.&lt;/p&gt;

&lt;p&gt;Require a Developer Account to register a Plugin, and take a cut of the profits from the sale (whether it be one-time, subscription, etc…)&lt;/p&gt;

&lt;p&gt;Instead of "Plugins" you may also consider charging by  "Feature", "Module", "Element", etc...&lt;/p&gt;

&lt;p&gt;Start by getting the registration / billing in place, and then work on the technical tools.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Tools
&lt;/h4&gt;

&lt;p&gt;Start by helping with hosting and code-signing.  Eventually, consider helpful tools to manage dependencies, check API impedance, custom rules, etc...&lt;/p&gt;

&lt;p&gt;While you may eventually build a UI in the App Store (including nested Plugins), allow developers to build custom store-fronts conforming to your design standards.&lt;/p&gt;

&lt;h4&gt;
  
  
  Loosen Restrictions
&lt;/h4&gt;

&lt;p&gt;Now that you're capturing the lost revenue, allow Plugins which are inconsistent with the original App intent.  If classification is the issue, you can derive the classification data from the fanout of statically assigned Plugins.  Consider support for dynamic assignment in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The details can be sorted, but the idea is to convert a gray area of the License Agreement to a greenfield opportunity.&lt;/p&gt;

&lt;p&gt;There are other issues to consider, like security, but given the language of the agreement, I tend to believe revenue loss is the biggest hindrance.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and I hope you'll re-consider first-class support for Extensible iOS Apps.&lt;/p&gt;

&lt;p&gt;Also, thanks for building these cute metal boxes with all these bright little lights.  They're pretty useful.&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
CR&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm building a Plugin Market for the Web and React Native.  For more, follow me on &lt;a href="https://github.com/CodalReef" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://dev.to/codalreef"&gt;Dev&lt;/a&gt;, &lt;a href="https://twitter.com/CodalReef" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/CodalReef" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Thanks to the Reddit users who responded to my &lt;a href="https://www.reddit.com/r/reactnative/comments/le9pqh/i_wrote_a_small_library_to_load_modules_including/" rel="noopener noreferrer"&gt;recent post&lt;/a&gt;. This feedback helped identify these License Agreement concerns.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm just a random guy with some thoughts, and nothing in this post is meant to be construed as legal advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>swift</category>
    </item>
    <item>
      <title>Plugin Oriented Design with Halia</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Sun, 31 Jan 2021 19:34:36 +0000</pubDate>
      <link>https://forem.com/orandaio/building-apps-as-a-tree-of-plugins-with-halia-1e5o</link>
      <guid>https://forem.com/orandaio/building-apps-as-a-tree-of-plugins-with-halia-1e5o</guid>
      <description>&lt;p&gt;Building apps with Plugin Oriented Design (POD) has a lot of advantages (discussed in my recent &lt;a href="https://dev.to/codalreef/pluggable-apps-with-lenny-the-duck-2oj3"&gt;blog post&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Today, I'd like to introduce "Halia", an extensible TS / JS Dependency Injection Framework built to manage "Plugins" in your apps.&lt;/p&gt;

&lt;p&gt;Building a Pluggable app has its advantages, but it can be challenging to stay organized when you're dynamically importing code that changes other code.&lt;/p&gt;

&lt;p&gt;For example, if you build a feature that depends upon another feature (or multiple), you'd likely end up "splitting and spreading" the feature across the app.&lt;/p&gt;

&lt;p&gt;Halia can help with this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/CodalReef/Halia" rel="noopener noreferrer"&gt;Halia - Extensible TS / JS Dependency Injection Framework&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%2Fi%2F4shk4wri1g7rsv0s4ceg.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%2Fi%2F4shk4wri1g7rsv0s4ceg.png" alt="Halia Logo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With Halia, your app's core is packaged as a Plugin, and  features are packaged as Plugins.  Each Plugin can then be extended by other Plugins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re also building tools to dynamically load plugins from a URL, activate plugins dynamically, user level managment (like a plugin store), cross-stack and cross-eco plugins, and contracts (for building re-usable plugins).&lt;/p&gt;

&lt;p&gt;Halia is responsible for managing this tree of plugins.  Let's see how it works with an example:&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;You have a duck that everyone loves:&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;//  duck-app.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDuck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quack&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;Everyone except Paul.  Paul wants a special &lt;strong&gt;🦄 Disco Duck 🦄&lt;/strong&gt;, so you make an update:&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;//  duck-app.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;Paul&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getDuck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Paul&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Michael Quackson&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;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quack&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;While the code works for Paul, it's become more complex, harder to read, and coupled with the "client" concept.&lt;/p&gt;

&lt;p&gt;Instead, we can use a Halia Plugin to encapsulate and inject this feature:&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;//  duck-app-plugin.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;DuckApp&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./duck-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DuckAppPlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HaliaPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;duckApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Duck App Plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;setGetDuck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getDuck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;DuckApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDuck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getDuck&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//  disco-duck-plugin.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;Paul&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client-list&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DiscoDuckPlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HaliaPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;discoDuck&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disco Duck Plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DuckAppPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;duckApp&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Paul&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;duckApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setGetDuck &lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Michael Quackson&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;Then we can build the stack and invoke the code:&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;//  main.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;HaliaStack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;Halia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DuckApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./DuckApp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DiscoFeature&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./DiscoFeature&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;buildApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;//  Initialize the Stack&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appStack&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;HaliaStack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;//  Register Plugins&lt;/span&gt;
  &lt;span class="nx"&gt;appStack&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="nx"&gt;DuckApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;appStack&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="nx"&gt;DiscoFeature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;//  Build the Stack&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;//  Call the Method&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duckApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getExports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DuckApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;duckApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logNoise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;buildApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, the original code is left in-tact and de-coupled.&lt;/p&gt;

&lt;p&gt;If Paul longer wants the &lt;strong&gt;🦄 Disco Duck 🦄&lt;/strong&gt;  we just don't register the Plugin.  If he needs an additional change, we have a namespace dedicated to his unique needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a simple example that can be solved in other ways.  However, it demonstrates the general idea, and as features become more complex, we've found this pattern helps to keep things organized.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For more info on Dependency Injection Frameworks (like Angular, Nest, and Halia) see our article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/codalreef/learn-dependency-injection-with-doug-the-goldfish-3j43"&gt;Dependency Injection with Doug the Goldfish 🐠&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%2Fi%2F3tbx7dihrh60d5poo4pt.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%2Fi%2F3tbx7dihrh60d5poo4pt.png" alt="Doug Image" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you enjoy the package and the associated concepts.&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
CR&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For more articles like this, follow me on:  &lt;a href="https://github.com/CodalReef" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://dev.to/codalreef"&gt;Dev&lt;/a&gt;, &lt;a href="https://twitter.com/CodalReef" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/CodalReef" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Build Extensible Apps with Lenny the Duck 🦆</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Sun, 31 Jan 2021 18:10:19 +0000</pubDate>
      <link>https://forem.com/orandaio/pluggable-apps-with-lenny-the-duck-2oj3</link>
      <guid>https://forem.com/orandaio/pluggable-apps-with-lenny-the-duck-2oj3</guid>
      <description>&lt;p&gt;Unlike many apps, "Extensible" apps can be extended with self-contained pockets of code called "Plugins".&lt;/p&gt;

&lt;p&gt;These apps tend to be modular by design, resulting in manageable, loosely-coupled code.&lt;/p&gt;

&lt;p&gt;Today, let's learn how to build extensible apps.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Every day, you probably use extensible apps:&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%2Fi%2Fbo7kfomrpsoxdgyj00v5.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%2Fi%2Fbo7kfomrpsoxdgyj00v5.png" alt="Alt Text" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your favorite development tools are probably extensible too:&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%2Fi%2Ff2sy21072ld1qhg2h8my.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%2Fi%2Ff2sy21072ld1qhg2h8my.png" alt="Alt Text" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem is, there are too many problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With Plugins, you don’t need to solve every problem.  Users solve their own problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With plugins, feature logic can be centralized instead of spread throughout the codebase.  This leads to modularized, loosely-coupled features.&lt;/p&gt;

&lt;p&gt;When you build your entire app as a “Tree of Plugins”, these benefits extend to the whole codebase.  Ultimately, this benefits you, your team, and your customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Non-Extensible Systems
&lt;/h2&gt;

&lt;p&gt;Imagine you're a duck named Lenny (🦆), and you love to quack.  Most of your friends love to quack too, except Lonnie (🍗).&lt;/p&gt;

&lt;p&gt;Anyways... you live in a park and people like to throw food at you (despite the litany of signs indicating not to).&lt;/p&gt;

&lt;p&gt;One day, you notice you’ve become quite plump.  So, you build a web service to track your consumption:&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;//  food-service.ts&lt;/span&gt;

&lt;span class="c1"&gt;//  Log of Foods Eaten&lt;/span&gt;
&lt;span class="c1"&gt;//  Example:  [{ name: "lenny", food: "waffle", calories: 5 }]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;//  Function to Log a Food (by Duck Name)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logFood&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&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;calories&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;foods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//  Function to Get Log (by Duck Name)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;foods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;food&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="c1"&gt;//  JS Module Exports&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;logFood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, tracking has given you the &lt;em&gt;bill&lt;/em&gt;-power to lose 3 ounces!&lt;/p&gt;

&lt;p&gt;That's great, but your friend Mack (🐦) has no self control.  So, he asks you to scare the humans with a horn once he exceeds his 300 calorie daily limit.&lt;/p&gt;

&lt;p&gt;Then your friend Jack (🐤) asks if you can also track protein.  He’s already fit, so he’s more concerned with staying jacked than losing fat.&lt;/p&gt;

&lt;p&gt;Before you know it, Abby (🦀), Tabby(🐢) and Doug (🐠) are asking for features.  Even Larry (🐊) wants something, and you're pretty sure he's the one who ate Lonnie (🍗)!&lt;/p&gt;

&lt;p&gt;The whole pond descends upon you, the backlog is full, and now the app is so complex that you're losing customers talking about "the good old days" when things were simple.&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%2Fi%2Fvmzb4pfet05dl816inlv.jpg" 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%2Fi%2Fvmzb4pfet05dl816inlv.jpg" alt="Alt Text" width="728" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you wake up... "Are you ok honey?", asks your wife Clara (🦆) as she waddles in with a basket of breadcrumbs.&lt;/p&gt;

&lt;p&gt;"I had the nightmare again...", you reply in an anxious tone.&lt;/p&gt;

&lt;p&gt;“Silly goose”, Clara chuckles and says: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pain of feature creep, non-modular code, and tightly coupled functionality can be largely avoided with plugin-oriented design (POD).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looking up to meet her gaze you say, "You're right dear.  let's recap the basics of plugin oriented design so we never forget."&lt;/p&gt;

&lt;p&gt;With a warm embrace Clara replies, "I can't think of a better way to spend our Sunday =)"&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Extensible Systems
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A fundamental characteristic of plugin-oriented design is the ability to alter functionality without altering the existing system definition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, to make your Food Service "extensible", you decide to do two things: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Register&lt;/strong&gt;:  Allow users to register custom functions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Invoke&lt;/strong&gt;:  Run the registered functions when a condition is met.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this, other developers can “inject” functionality into your app.&lt;/p&gt;

&lt;p&gt;These registration points are called &lt;a href="https://github.com/webpack/tapable" rel="noopener noreferrer"&gt;Hooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We see this pattern everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wordpress&lt;/strong&gt;:  &lt;a href="https://github.com/WordPress/WordPress/blob/37662df05e948eaf77b17e6fd27d519987f179db/wp-includes/class-wp-hook.php#L73" rel="noopener noreferrer"&gt;"Filters"&lt;/a&gt; and &lt;a href="https://github.com/WordPress/WordPress/blob/37662df05e948eaf77b17e6fd27d519987f179db/wp-includes/plugin.php#L409" rel="noopener noreferrer"&gt;"Actions (&lt;em&gt;gasp&lt;/em&gt; it's just another Filter)"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Babel&lt;/strong&gt;: &lt;a href="https://github.com/babel/babel/blob/main/packages/babel-core/src/config/plugin.js#L23" rel="noopener noreferrer"&gt;Visitor Plugin Function&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webpack&lt;/strong&gt;:  &lt;a href="https://github.com/webpack/tapable" rel="noopener noreferrer"&gt;Tapable&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I recommend taking a look at &lt;code&gt;tapable&lt;/code&gt;.  This is the small module underlying every Webpack Plugin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the Food Service code updated to use Hooks:&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;//  extensible-food-service.ts&lt;/span&gt;

&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//  Define the Hook&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LogFoodFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&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;calories&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;props&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="c1"&gt;//  List of Functions Registered to this "Hook"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogFoodFunction&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="c1"&gt;//  Add a Function to the Hook&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogFoodFunction&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;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//  Build the Food Service&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;

&lt;span class="c1"&gt;//  List of Foods Eaten&lt;/span&gt;
&lt;span class="c1"&gt;//  Example:  [{ name: "lenny", food: "bread", calories: 5 }]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;//  Add the Core Function&lt;/span&gt;
&lt;span class="nf"&gt;addFunction&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calories&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;foods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calories&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//  Function to Log a Food (by Duck Name)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logFood&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&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;calories&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//  Trigger Functions in the Register&lt;/span&gt;
  &lt;span class="nx"&gt;functions&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;func&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//  Function to Get Log (by Duck Name)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;foods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;food&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="c1"&gt;//  JS Module Exports&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;logFood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addFunction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, anyone can extend this JS Module by calling &lt;code&gt;addFunction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s Macks’s (🐦) Plugin to scare humans with a horn:&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;//  macks-plugin.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;FoodService&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;extensible-food-service&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Horn&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;horn-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//  Set Calorie Limit&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calorieLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;FoodService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addFunction&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="c1"&gt;//  Get Total Calories&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eatenCalories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FoodService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;//  Check Condition&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;eatenCalories&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;calorieLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Horn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blow&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;Now, all you need to do is import Mack's Plugin, and the feature will be integrated.&lt;/p&gt;

&lt;p&gt;However, building a system with “Hooks” is just one way to implement “POD” principles.&lt;/p&gt;

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

&lt;p&gt;Hooks (and their variants) are fairly common.  Probably because they're simple:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Build a way to register code, and invoke the code when a condition is met.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But, they're not the only way to build an extensible system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Primitive Domain
&lt;/h4&gt;

&lt;p&gt;In the code above, we register "primitive" code with a Hook.  Fundamentally, primitive code is just an &lt;em&gt;encoding&lt;/em&gt; of intent.  In this case, it's then &lt;em&gt;decoded&lt;/em&gt; by the JS runtime.&lt;/p&gt;

&lt;h4&gt;
  
  
  Application Domain
&lt;/h4&gt;

&lt;p&gt;However, intent can be &lt;em&gt;encoded&lt;/em&gt; in other ways too.  For example, you can build your own language.  It sounds complicated, but it's exactly what you do when you define classes or build an API.  Your application logic is then responsible for managing and &lt;em&gt;decoding&lt;/em&gt; entities in this domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  External Domain
&lt;/h4&gt;

&lt;p&gt;In some cases, you may want to externalize the entire process.  For example, you can trigger &lt;em&gt;external&lt;/em&gt; code with Webhooks, Websockets, and tools like IFTTT, Zapier, and Shortcuts.  &lt;/p&gt;

&lt;p&gt;Regardless of the implementation, it helps to remember this &lt;em&gt;golden&lt;/em&gt; principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep it simple.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;a.k.a. don't do more than reasonably necessary&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This applies to you, your team, your functions, modules, app, and everything you touch.  If something is too complex, try to break it up.  Refactor, rework, and fundamentalize as necessary. &lt;/p&gt;

&lt;p&gt;Plugin-Oriented Design (POD) can help achieve this goal, especially as logic becomes complex.  By modeling each feature as a Plugin, complexity only bubbles up when necessary, and in a predictable, modularized container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook Concerns
&lt;/h2&gt;

&lt;p&gt;There are several concerns with the hook implementation we built above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centrality&lt;/strong&gt;:  You're responsible for loading Plugins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust&lt;/strong&gt;:  You're responsible for auditing code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflicts&lt;/strong&gt;:  Users may disagree on the feature set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt;:  No management system for complex dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More&lt;/strong&gt;:  A whole lot more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These concerns can be addressed using various strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;External Plugins&lt;/strong&gt;:  Dynamically inject code from an external resource (like a URL) at runtime.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Contextual Activation&lt;/strong&gt;:  Dynamically activate features based on the current context (logged in users, application state, etc...)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Plugin Managers&lt;/strong&gt;:  Coordinates feature extension, even in a complex network of dependencies.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;More&lt;/strong&gt;:  A whole lot more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope to cover "External Plugins", "Contextual Activation", and related topics in future articles.&lt;/p&gt;

&lt;p&gt;To learn about "Plugin Managers" and how our new tool "Halia" can help you build Extensible JS / TS systems, see our blog post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/codalreef/building-apps-as-a-tree-of-plugins-with-halia-1e5o"&gt;Plugin Oriented Design with Halia&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The concepts discussed here are just the start.  We've opened a can of worms, but for now, let's put the worms back in the can. We've already overfed the park animals.&lt;/p&gt;

&lt;p&gt;Speaking of which, we found Lonnie (🦆)!  It turns out she was just across the pond learning plugin-oriented architecture (like all good ducks do).&lt;/p&gt;

&lt;p&gt;In closing, there are plenty of ways to cook your goose, so you might as well be a duck (🦆).&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
CR&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For more articles like this, follow me on:  &lt;a href="https://github.com/CodalReef" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://dev.to/codalreef"&gt;Dev&lt;/a&gt;, &lt;a href="https://twitter.com/CodalReef" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/CodalReef" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Dependency Injection with Doug the Goldfish 🐠</title>
      <dc:creator>Oranda IO</dc:creator>
      <pubDate>Tue, 26 Jan 2021 23:14:14 +0000</pubDate>
      <link>https://forem.com/orandaio/learn-dependency-injection-with-doug-the-goldfish-3j43</link>
      <guid>https://forem.com/orandaio/learn-dependency-injection-with-doug-the-goldfish-3j43</guid>
      <description>&lt;p&gt;There are lots of great strategies to keep code manageable and extensible.  Today, let's learn about "Dependency Injection". &lt;/p&gt;

&lt;h1&gt;
  
  
  Dependency Injection
&lt;/h1&gt;

&lt;p&gt;Imagine you're a goldfish named Doug (🐠), and you love bubbles.  So much so, that you bought a Bubble Machine with a programmable Typescript SDK.&lt;/p&gt;

&lt;p&gt;You write a program to make bubbles when you wake up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bubbler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bubbler&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;initBubbler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;//  Instantiate&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bubbler&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;Bubbler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dougs-bubbler&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 Bubbler&lt;/span&gt;
  &lt;span class="nx"&gt;bubbler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bubble&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7:00AM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8:00AM&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;initBubbler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now you awaken to fresh, well-oxygenated water 💦&lt;/p&gt;

&lt;p&gt;You tell your friend Mary (🐟), and she's so excited, she buys a bubbler too.&lt;/p&gt;

&lt;p&gt;You update the code to initialize both bubblers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bubbler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bubbler&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;initDougsBubbler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bubbler&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;Bubbler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dougs-bubbler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;bubbler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bubble&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7:00AM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8:00AM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initMarysBubbler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bubbler&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;Bubbler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marys-bubbler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;bubbler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bubble&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7:00AM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8:00AM&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;initDougsBubbler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;initMarysBubbler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, but there's something... fishy... going on here...&lt;/p&gt;

&lt;p&gt;Instead of duplicating the &lt;code&gt;initBubbler&lt;/code&gt; function, you could have "hoisted" the instantiation step outside the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bubbler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bubbler&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;dougsBubbler&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;Bubbler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dougs-bubbler&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;marysBubbler&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;Bubbler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marys-bubbler&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;initBubbler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bubbler&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;bubbler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bubble&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7:00AM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8:00AM&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;initBubbler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dougsBubbler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;initBubbler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;marysBubbler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we only need the single &lt;code&gt;initBubbler&lt;/code&gt; function, even if your friends Larry (🐙) and Barry (🐡) decide to buy Bubblers too.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initBubbler&lt;/code&gt; function is no longer responsible for constructing a &lt;code&gt;bubbler&lt;/code&gt; instance.  Instead, it's &lt;strong&gt;injected&lt;/strong&gt; into the function from the outer scope.  This pattern is called "Dependency Injection" (DI).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dependency Injection is a great way to keep your functions simple, modular, and re-usable by delegating responsibility to the caller.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Inversion of Control
&lt;/h3&gt;

&lt;p&gt;Further, because the "caller" is now responsible for initializing the Bubbler (instead of the &lt;code&gt;initBubbler&lt;/code&gt; function), we say control has been "inverted".  Dependency Injection is a means by which to achieve "Inversion of Control" (IoC).&lt;/p&gt;

&lt;h3&gt;
  
  
  IoC Container
&lt;/h3&gt;

&lt;p&gt;The outer scope, responsible for instantiating the &lt;code&gt;bubbler&lt;/code&gt; dependency, is called the "Inversion of Control Container" (IoC Container).&lt;/p&gt;

&lt;h3&gt;
  
  
  DI Frameworks
&lt;/h3&gt;

&lt;p&gt;You can use a "DI Framework" to make things even easier.  Instead of manually initializing the dependencies, a DI Framework acts as the IoC Container and does the work for you.&lt;/p&gt;

&lt;p&gt;You just tell the framework which dependencies your function needs, and once they're initialized, the framework automatically invokes your function.&lt;/p&gt;

&lt;p&gt;Angular and Nest are two popular tools that include DI Frameworks.  Both of these helped in the writing of this article and shaping my own understanding of DI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular:  &lt;a href="https://angular.io/guide/providers" rel="noopener noreferrer"&gt;https://angular.io/guide/providers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nest:  &lt;a href="https://docs.nestjs.com/fundamentals/custom-providers" rel="noopener noreferrer"&gt;https://docs.nestjs.com/fundamentals/custom-providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Plugins
&lt;/h3&gt;

&lt;p&gt;DI Frameworks are great for keeping code organized.  However, I like to go one step further and build a module for every "Feature" in my app. &lt;/p&gt;

&lt;p&gt;When the DI Framework initializes the "Feature Module", it "installs" itself by invoking dependency methods.  It then exports its own API for dependencies to install themselves.&lt;/p&gt;

&lt;p&gt;We call call these modules "Plugins", because they inject functionality back into the app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By building apps as a "Tree of Plugins", feature code is centralized instead of being spread throughout the app. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This makes it easy to mix and match features, build new features, and even open your app for extension by external developers (like Wordpress does).&lt;/p&gt;

&lt;p&gt;To learn more about building apps as a tree of Plugins, check out my new package "Halia":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/CodalReef/Halia" rel="noopener noreferrer"&gt;Halia - Extensible TS / JS Dependency Injection Framework&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Halia is a simple, lightweight, and extensible DI Framework.  It's not tied to a particular backend / frontend technology, and you can customize the framework by installing "Plugins".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We hope your time spent as Doug has helped you see value in the DI Pattern and DI Frameworks.&lt;/p&gt;

&lt;p&gt;If you'd like, you can stop imagining you're a goldfish and resume normal human function.&lt;/p&gt;

&lt;p&gt;Or, you can imagine you’re a duck and learn how to build Pluggable Apps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/codalreef/pluggable-apps-with-lenny-the-duck-2oj3"&gt;Build Pluggable Apps with Lenny the Duck 🦆&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%2Fi%2Fd860i5k7g5txl2ycrudr.jpg" 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%2Fi%2Fd860i5k7g5txl2ycrudr.jpg" alt="Alt Text" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All thoughts and comments are greatly appreciated =)&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
CR&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For more articles like this, follow me on:  &lt;a href="https://github.com/CodalReef" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://dev.to/codalreef"&gt;Dev&lt;/a&gt;, &lt;a href="https://twitter.com/CodalReef" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.reddit.com/user/CodalReef" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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