<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jo Franchetti</title>
    <description>The latest articles on DEV Community by Jo Franchetti (@thisisjofrank).</description>
    <link>https://dev.to/thisisjofrank</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%2F331818%2Fe0094f5c-f975-4712-96df-a1f894459421.jpg</url>
      <title>DEV Community: Jo Franchetti</title>
      <link>https://dev.to/thisisjofrank</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thisisjofrank"/>
    <language>en</language>
    <item>
      <title>npm install &amp;&amp; pray</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Thu, 12 Mar 2026 15:12:18 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/npm-install-pray-3331</link>
      <guid>https://dev.to/thisisjofrank/npm-install-pray-3331</guid>
      <description>&lt;p&gt;There’s a ritual every JavaScript developer knows.&lt;/p&gt;

&lt;p&gt;You’re building something. You need to parse a date, validate an email, or format a number. So you do what any reasonable person does, you open a terminal, type &lt;code&gt;npm install&lt;/code&gt;, pick a package with four million weekly downloads and a friendly README, and move on.&lt;/p&gt;

&lt;p&gt;For a long time, that felt safe. The openness of npm and the kindess of opensource developers helped turn the JS ecosystem into something genuinely remarkable. We had a global library of shared effort, where someone could publish a utility at 2am and developers on the other side of the world could be using it in production by morning.&lt;/p&gt;

&lt;p&gt;But the openness and expansiveness of the JavaScript ecosystem was built on trust. And, sadly, that trust is constantly being eroded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worm that changed the mood
&lt;/h2&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%2Fa9vxlo4vu4yl1wgv2inc.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%2Fa9vxlo4vu4yl1wgv2inc.png" alt="The Sandworm from Dune" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In September 2025, &lt;a href="https://www.reversinglabs.com/blog/shai-hulud-worm-npm" rel="noopener noreferrer"&gt;ReversingLabs documented the first known self-replicating worm&lt;/a&gt; in the npm ecosystem. They traced the initial compromise to &lt;code&gt;rxnt-authentication@0.0.3&lt;/code&gt;, then watched the malware spread through compromised maintainer accounts into hundreds of packages.&lt;/p&gt;

&lt;p&gt;A couple of months later, &lt;a href="https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/" rel="noopener noreferrer"&gt;Datadog described a second wave&lt;/a&gt;: Shai-Hulud 2.0. That campaign backdoored &lt;strong&gt;796&lt;/strong&gt; npm packages totaling more than &lt;strong&gt;20 million&lt;/strong&gt; weekly downloads.&lt;/p&gt;

&lt;p&gt;The mechanics were elegant in the worst possible way. Infect one developer, steal their npm credentials, then publish tainted versions of other packages they maintain, and let every new victim become the next distribution channel. The malware hunted for environment variables, cloud tokens, GitHub credentials, and npm auth tokens, then exfiltrated what it found while the application appeared to run normally. The infected developers none the wiser.&lt;/p&gt;

&lt;p&gt;Then came the phishing attack against maintainer Josh Junon (&lt;code&gt;qix&lt;/code&gt;). Attackers used a fake &lt;code&gt;npmjs.help&lt;/code&gt; domain and a convincing support email to steal publishing credentials. From there, they &lt;a href="https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk" rel="noopener noreferrer"&gt;pushed malicious versions of widely used packages&lt;/a&gt; including &lt;code&gt;chalk&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;ansi-styles&lt;/code&gt;, &lt;code&gt;strip-ansi&lt;/code&gt;, and other foundational libraries. At the time of disclosure, the affected packages accounted for roughly &lt;strong&gt;2.6 billion&lt;/strong&gt; weekly downloads.&lt;/p&gt;

&lt;p&gt;Not a particularly sophisticated exploit. Just a phishing email and the right publishing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps working
&lt;/h2&gt;

&lt;p&gt;Supply-chain attacks succeed because the trust model still assumes the ecosystem is friendlier and safer than it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version ranges put trust on autopilot
&lt;/h3&gt;

&lt;p&gt;When your &lt;code&gt;package.json&lt;/code&gt; says &lt;code&gt;^2.1.0&lt;/code&gt;, your CI can quietly pull in &lt;code&gt;2.1.1&lt;/code&gt; the moment it ships. That’s convenient! It means a maintainer’s latest publish can slide straight into your build without a human ever reviewing it, but you are implicitly trusting that every update is safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  The soft target is the maintainer, not the package
&lt;/h3&gt;

&lt;p&gt;The attack on Qix didn’t require finding a flaw in &lt;code&gt;chalk&lt;/code&gt; or &lt;code&gt;debug&lt;/code&gt;. It required compromising the person who could publish them. Once the account falls, all downstream trust falls with it. Opensource maintainers are only human, and often tired and overworked humans. That makes them a soft target, and the attack surface is huge: npm has millions of maintainers, many of whom have access to publish widely used packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install-time execution is an attacker’s dream
&lt;/h3&gt;

&lt;p&gt;npm lifecycle scripts like &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;install&lt;/code&gt;, and &lt;code&gt;postinstall&lt;/code&gt; run during installation. That means malicious code can execute before you import anything, before your tests run, and before you’ve even finished reading the package name. Shai-Hulud took advantage of exactly that kind of install-time execution.&lt;/p&gt;

&lt;p&gt;And the malicious behavior doesn’t need to look dramatic. A lifecycle script, a conditional payload, a quietly spawned subprocess, a snippet that only activates when certain environment variables exist — all of that can slip past casual review.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI has widened the blast radius
&lt;/h2&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%2F6xlvfbtuv0c547mxqeb4.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%2F6xlvfbtuv0c547mxqeb4.png" alt="Depiction of an ai robot pointing at some code in an editor" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI coding assistants are genuinely useful. I use them. You probably do too. They’re great at boilerplate, they explain unfamiliar code faster than docs, and they often catch the kind of obvious mistakes that cost real time.&lt;/p&gt;

&lt;p&gt;But they also create a new category of trust problem: code you didn’t write, don’t fully understand, and may run too quickly.&lt;/p&gt;

&lt;p&gt;In February 2025, &lt;a href="https://trufflesecurity.com/blog/research-finds-12-000-live-api-keys-and-passwords-in-deepseek-s-training-data" rel="noopener noreferrer"&gt;Truffle Security reported&lt;/a&gt; that a scan of the December 2024 Common Crawl archive found roughly 12,000 live API keys and passwords embedded in public web data — the same kind of data that can end up in model training pipelines.&lt;/p&gt;

&lt;p&gt;In May 2025, &lt;a href="https://krebsonsecurity.com/2025/05/xai-dev-leaks-api-key-for-private-spacex-tesla-llms/" rel="noopener noreferrer"&gt;KrebsOnSecurity reported&lt;/a&gt; that an xAI employee exposed a private API key on GitHub that could access private or fine-tuned models tied to internal data from companies including SpaceX and Tesla. In November 2025, &lt;a href="https://www.wiz.io/blog/forbes-ai-50-leaking-secrets" rel="noopener noreferrer"&gt;Wiz reported&lt;/a&gt; that 65% of companies it analyzed from the Forbes AI 50 had verified secret leaks on GitHub.&lt;/p&gt;

&lt;p&gt;Even the companies and systems &lt;strong&gt;building&lt;/strong&gt; AI tools suffer from the same secret management failures as everyone else!&lt;/p&gt;

&lt;p&gt;The second problem is more mundane and, in practice, more common. AI-generated code fails in ordinary ways. A cleanup script deletes the wrong directory. A migration points at the wrong database. A one-line “helper” turns into a subtle security bug. These aren’t exciting science-fiction failures. They’re the normal failures of code produced someone that has no real understanding of your environment (which is what an AI assistant is).&lt;/p&gt;

&lt;p&gt;And once that code runs with your local permissions, a mistake isn’t just a bug. It can be data loss, credential exposure, or an accidental production incident.&lt;/p&gt;

&lt;p&gt;We should also talk about prompt injection.&lt;/p&gt;

&lt;p&gt;In August 2025, researcher Johann Rehberger showed that &lt;a href="https://www.promptarmor.com/resources/claude-cowork-exfiltrates-files" rel="noopener noreferrer"&gt;Claude Code could be tricked&lt;/a&gt; by an indirect prompt injection into reading sensitive local files and leaking their contents through DNS requests. Around the same time, &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-53773" rel="noopener noreferrer"&gt;GitHub detailed prompt-injection risks&lt;/a&gt; in VS Code and Copilot agent mode, including the possibility of leaking tokens, exposing confidential files, or even executing code without the user’s explicit approval.&lt;/p&gt;

&lt;p&gt;The attack surface isn’t just the packages you install anymore. It’s also the code, issues, pull requests, docs, and comments your assistant is asked to look at.&lt;/p&gt;

&lt;p&gt;Model poisoning is also a real, though still somewhat experimental, threat. Researchers have shown that injecting as few as &lt;a href="https://www.anthropic.com/research/small-samples-poison" rel="noopener noreferrer"&gt;&lt;strong&gt;250 malicious documents&lt;/strong&gt; into pre-training data can successfully backdoor an LLM&lt;/a&gt; with 90% attack success rates. You wouldn't know the model was compromised. It would behave normally except when specific conditions were met.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defence-in-depth
&lt;/h2&gt;

&lt;p&gt;This is the part where it’s worth being concrete, because there &lt;strong&gt;are&lt;/strong&gt; real defences. They just require a different default mindset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Least-privilege execution matters.
&lt;/h3&gt;

&lt;p&gt;Deno’s permission model is a meaningful improvement here. By default, a Deno program cannot read environment variables, touch the filesystem, or make outbound network requests unless you allow it.&lt;/p&gt;

&lt;p&gt;So when a malicious payload tries to read &lt;code&gt;HOME&lt;/code&gt; or scrape your environment, the runtime can stop it cold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;┏ ⚠️  Deno requests &lt;span class="nb"&gt;env &lt;/span&gt;access.
┠─ To see a stack trace &lt;span class="k"&gt;for &lt;/span&gt;this prompt, &lt;span class="nb"&gt;set &lt;/span&gt;the DENO_TRACE_PERMISSIONS environmental variable.
┠─ Learn more at: https://docs.deno.com/go/--allow-env
┠─ Run again with &lt;span class="nt"&gt;--allow-env&lt;/span&gt; to bypass this prompt.
┗ Allow? &lt;span class="o"&gt;[&lt;/span&gt;y/n/A] &lt;span class="o"&gt;(&lt;/span&gt;y &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;, allow&lt;span class="p"&gt;;&lt;/span&gt; n &lt;span class="o"&gt;=&lt;/span&gt; no, deny&lt;span class="p"&gt;;&lt;/span&gt; A &lt;span class="o"&gt;=&lt;/span&gt; allow all &lt;span class="nb"&gt;env &lt;/span&gt;permissions&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Frfdcodx9uenjn9entq6j.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%2Frfdcodx9uenjn9entq6j.png" alt="screenshot of the deno security protocols warning about sensitive API use" width="720" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this is old news, we all already know about &lt;a href="https://docs.deno.com/runtime/fundamentals/security/" rel="noopener noreferrer"&gt;Deno's permissions model&lt;/a&gt;, and it isn’t a complete solution. If code legitimately needs network or environment access, you still have to grant it. But it does shrink the default blast radius in a way Node.js traditionally has not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat AI-generated code as untrusted code.
&lt;/h3&gt;

&lt;p&gt;That’s the mindset shift that matters most. Running model-generated code with the same implicit trust you give your own code feels normal, but it isn’t. You’re executing output from a system that may have learned from insecure examples, can misunderstand your environment, and can be steered by untrusted content it was asked to analyse. It doesn't have your best interests at heart, not because it is malicious by nature, but because it just doesn't know.&lt;/p&gt;

&lt;h3&gt;
  
  
  For execution you don’t control, use real isolation.
&lt;/h3&gt;

&lt;p&gt;The gold standard is running untrusted code somewhere with no ambient access to your host filesystem, host secrets, or unrestricted network.&lt;/p&gt;

&lt;p&gt;Docker can do this, but there’s real workflow friction there: building images, managing lifecycle, mounting the right files, and keeping the whole setup ergonomic enough that you’ll actually use it.&lt;/p&gt;




&lt;h2&gt;
  
  
  A solution - isolated dev environments
&lt;/h2&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%2Fx4ttpsoo32vp6lgzglfm.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%2Fx4ttpsoo32vp6lgzglfm.png" alt="A Deno dinosaur playing in a sandpit" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One emerging option is &lt;a href="https://deno.com/deploy/sandbox" rel="noopener noreferrer"&gt;Deno Deploy Sandbox&lt;/a&gt;, which provides programmatic access to Firecracker-based Linux microVMs. These aren’t just containers with nicer branding. Each sandbox gets its own filesystem, network stack, and process tree, and the platform advertises startup times under 200 milliseconds — fast enough to use inside an agent loop.&lt;/p&gt;

&lt;p&gt;Here’s what that looks like in practice: ask a model to write some code, run it in a sandbox, and make sure your secrets can be used only where you intend.&lt;/p&gt;

&lt;p&gt;Below is an example script that asks Claude to write a Deno script that fetches the current Bitcoin price from the CoinGecko API, then runs it in a sandbox with no permissions except outbound network access to &lt;code&gt;api.coingecko.com&lt;/code&gt;. Even if the generated code is malicious, it can only talk to that one host, and it can’t access any secrets or your local filesystem:&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;// Step 1: Import the Anthropic SDK and the Deno Sandbox SDK.&lt;/span&gt;
&lt;span class="c1"&gt;// The Anthropic SDK lets us talk to Claude, and @deno/sandbox gives us&lt;/span&gt;
&lt;span class="c1"&gt;// a secure microVM to run untrusted code in.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&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;npm:@anthropic-ai/sdk&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;Sandbox&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsr:@deno/sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Create an Anthropic client.&lt;/span&gt;
&lt;span class="c1"&gt;// It automatically picks up ANTHROPIC_API_KEY from the environment.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;Anthropic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: Ask Claude to write some code for us.&lt;/span&gt;
&lt;span class="c1"&gt;// We're asking for a complete, runnable Deno script — Claude will return&lt;/span&gt;
&lt;span class="c1"&gt;// it wrapped in a markdown code block.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-opus-4-6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Write a Deno script that fetches the current Bitcoin price from the CoinGecko API and prints it.&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="c1"&gt;// Step 4: Pull the generated code out of Claude's response.&lt;/span&gt;
&lt;span class="c1"&gt;// The response is an array of content blocks — we check it's a text block,&lt;/span&gt;
&lt;span class="c1"&gt;// then strip the markdown fences to get the raw source.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;firstBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unexpected content type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatedCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//          console.log("Generated code:\n", generatedCode);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 5: Create a sandbox — a fully isolated Linux microVM.&lt;/span&gt;
&lt;span class="c1"&gt;// "await using" means it's automatically destroyed when this scope ends,&lt;/span&gt;
&lt;span class="c1"&gt;// so we never leak resources even if something throws.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Step 6: Write the AI-generated code into the sandbox filesystem.&lt;/span&gt;
&lt;span class="c1"&gt;// The sandbox has its own isolated filesystem — nothing here touches the host.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeTextFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tmp/generated.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generatedCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 7: Run the code inside the sandbox with Deno.&lt;/span&gt;
&lt;span class="c1"&gt;// We only grant network access to the one host the code actually needs.&lt;/span&gt;
&lt;span class="c1"&gt;// stdout and stderr are piped so we can capture and display them.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deno&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&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;run&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;--allow-net=api.coingecko.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Only the specific host we expect&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tmp/generated.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;piped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;piped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Step 8: Set a hard timeout.&lt;/span&gt;
&lt;span class="c1"&gt;// AI-generated code could accidentally (or maliciously) loop forever —&lt;/span&gt;
&lt;span class="c1"&gt;// this ensures we kill the process after 10 seconds no matter what.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 9: Wait for the process to finish and print the results.&lt;/span&gt;
&lt;span class="c1"&gt;// output.stdoutText / stderrText are pre-decoded UTF-8 strings.&lt;/span&gt;
&lt;span class="c1"&gt;// output.status.success is true only if the exit code was 0.&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Output:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdoutText&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderrText&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No error output&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Helper: extract the first code block from a markdown string.&lt;/span&gt;
&lt;span class="c1"&gt;// Falls back to returning the raw text if no fences are found.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/``&lt;/span&gt;&lt;span class="err"&gt;`
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="nx"&gt;typescript&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;S&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="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```/);
  return match ? match[1] : text;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's happening here: if you run &lt;a href="https://docs.deno.com/examples/sandbox_for_untrusted_code/" rel="noopener noreferrer"&gt;this script&lt;/a&gt;, you will get some AI gen code, which you have not read and not checked, &lt;strong&gt;but&lt;/strong&gt; it can only reach &lt;code&gt;api.coingecko.com&lt;/code&gt;. It is running on an entirely separate machine from your own. The generated code can't read your &lt;code&gt;.env&lt;/code&gt; file. It can't write to your own filesystem. It can't call home to anywhere unexpected. And if it runs longer than 10 seconds, it gets killed.&lt;/p&gt;

&lt;p&gt;The point is the layering.&lt;/p&gt;

&lt;p&gt;The microVM keeps generated code away from your host OS. The &lt;code&gt;--allow-net&lt;/code&gt; flag limits outbound traffic to &lt;strong&gt;exactly&lt;/strong&gt; the host the script needs. You're giving yourself layers of safety.&lt;/p&gt;

&lt;p&gt;And Deno sandboxes offer one more layer that’s worth mentioning, that we don't use in this example: they have a built-in secret management system. You can create secrets in the sandbox configuration with a &lt;code&gt;secrets&lt;/code&gt; field. The value of the secret is replaced by a placeholder string inside the sandbox environment. The real values are only injected at the network layer when the code tries to send them to the specific hosts you allow. So even if the generated code tries to exfiltrate secrets, it can only send the placeholder values, which are useless to an attacker!&lt;/p&gt;

&lt;p&gt;Imagine the following:&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;await&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;hosts&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;api.anthropic.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// Only allow this secret to be sent to the Anthropic API&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="s2"&gt;`echo $ANTHROPIC_API_KEY`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output key will look like: &lt;code&gt;DENO_SECRET_PLACEHOLDER_55eb59627bc5700ae19f371791e0f54ad3204b745afcdc60acd66c23&lt;/code&gt;. Your key is nice and safe.&lt;/p&gt;

&lt;p&gt;And yes, these are somewhat contrived examples, but I wanted something to show you quickly how easy it is to spin up and run code in a Deno Sandbox.&lt;/p&gt;

&lt;p&gt;Since these sandboxes are full linux vms, you could of course install Claude directly on the sandbox and use that to generate code, rather than generating it on your local machine. Sandboxes allow for &lt;a href="https://docs.deno.com/sandbox/volumes/" rel="noopener noreferrer"&gt;snapshotting of a vm image&lt;/a&gt;, so you could, absolutely, install Claude and any other tools or software you might want, snapshot the VM and then spin multiple versions of that up in 200ms allowing really fast execution times for your Claude prompts and code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually needs to change
&lt;/h2&gt;

&lt;p&gt;The tools are getting better. Our habits need to catch up.&lt;/p&gt;

&lt;p&gt;We built a lot of modern JavaScript on the assumption that convenience deserved the benefit of the doubt. In the early days of npm, that mostly worked. The ecosystem was smaller, the incentives were different, and the threat model was simpler.&lt;/p&gt;

&lt;p&gt;That isn’t the world we operate in now.&lt;/p&gt;

&lt;p&gt;Supply-chain attacks are organized, repeatable, and economically attractive. AI assistants are generating code that runs with your permissions but without your oversight. Prompt injection has moved from theory to working demonstrations. And when something malicious does land, it usually goes after the credentials that unlock everything else.&lt;/p&gt;

&lt;p&gt;The answer is not to stop using npm or to stop using AI assistants. Both are too useful, and neither is going away.&lt;/p&gt;

&lt;p&gt;The answer is to stop giving code implicit trust just because it arrived through a convenient path.&lt;/p&gt;

&lt;p&gt;Run code with the minimum permissions it needs. Treat generated code as untrusted until it earns more trust. For anything you don’t fully control, always execute it in isolation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install &amp;amp;&amp;amp; pray&lt;/code&gt; used to be a joke about dependency sprawl. It now reads more like a description of the default security model.&lt;/p&gt;

&lt;p&gt;That model needs to change. Stay safe out there!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Using AI to prototype games in the browser</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Mon, 01 Dec 2025 14:19:46 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/using-ai-to-prototype-games-in-the-browser-41j1</link>
      <guid>https://dev.to/thisisjofrank/using-ai-to-prototype-games-in-the-browser-41j1</guid>
      <description>&lt;p&gt;There's a lot of discourse around the use of AI in games (and elsewhere!) at the moment. It's understandable, games are more expensive to make than ever, and teams are stretched thin trying to deliver bigger worlds, richer stories, and more reactive systems. Generative AI as a solution has landed with a mix of hype, suspicion, and legitimate curiosity. Many developers are still trying to make sense of where it actually fits into their existing workflows.&lt;/p&gt;

&lt;p&gt;I've been experimenting with how large language models can help prototype narrative games in the browser, to see what happens when you mix modern LLMs with the kind of structure that is already used for procedural content. Expanding the toolbox, as it were. I'm not trying to replace writers or designers, instead I hope to augment their work. As a developer I honestly need the help writing, and as a player I want to explore more worlds!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in this post?
&lt;/h2&gt;

&lt;p&gt;This post walks through building a simple prototype. We'll look at past uses of AI in games, cover using local models, and how to build a templated system for creating worlds and stories.&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%2Ffn4adbvjspliv5u9rp6f.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%2Ffn4adbvjspliv5u9rp6f.png" alt="An example of some of the generated world data" width="800" height="1034"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI in games
&lt;/h2&gt;

&lt;p&gt;AI in games is not new. Game developers have been using some form of AI almost from day one. The term “AI” itself is a bit of a catch-all, covering everything from simple decision trees to statistical models with billions of parameters. Games like Pong and Space Invaders had basic rule-based systems to control 'enemy' movement, Rogue and Elite introduced &lt;a href="https://en.wikipedia.org/wiki/Procedural_generation" rel="noopener noreferrer"&gt;procedural generation&lt;/a&gt; to create varied experiences within limited memory.&lt;br&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%2Fa1sjr3u7waxsjq679lry.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%2Fa1sjr3u7waxsjq679lry.png" alt="Screenshots of Rogue and Elite" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As games grew more complex, so did the AI techniques used. Classic game dev techniques that use procedural generation include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Pathfinding" rel="noopener noreferrer"&gt;Path-finding&lt;/a&gt; - the moving of a character or NPC from point A to point B while avoiding obstacles. This often involves some variant of the &lt;a href="https://en.wikipedia.org/wiki/A*_search_algorithm" rel="noopener noreferrer"&gt;A* search algorithm&lt;/a&gt;, or the use of navigation meshes in 3D spaces to guide NPCs around levels.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Artificial_intelligence_in_video_games#Procedural_content_generation" rel="noopener noreferrer"&gt;Procedural content generation&lt;/a&gt; - creating levels, items, or even entire worlds algorithmically. From &lt;em&gt;Rogue&lt;/em&gt;'s procedural dungeons to &lt;em&gt;Minecraft&lt;/em&gt;'s infinite voxel worlds and the vast cosmos of &lt;em&gt;No Man's Sky&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Artificial_intelligence_in_video_games#Narratives_and_gameplay_roles" rel="noopener noreferrer"&gt;Gameplay AI&lt;/a&gt; - gives NPCs a semblance of intelligence, using finite state machines, behaviour trees, or utility systems.&lt;/li&gt;
&lt;li&gt;More bespoke approaches like &lt;em&gt;Left 4 Dead&lt;/em&gt;'s &lt;strong&gt;Director system&lt;/strong&gt;, which adjusts pacing and difficulty dynamically based on the player's performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8hs6fqy9x7l1g4ceizg.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%2Fa8hs6fqy9x7l1g4ceizg.png" alt="Screenshots of Minecraft and No Man's Sky" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Generative_artificial_intelligence" rel="noopener noreferrer"&gt;Generative AI&lt;/a&gt; then introduces a new layer to this landscape.LLMs can be used to generate NPC dialogue, quests, or even entire story arcs on the fly. This is a step beyond traditional procedural generation and relies heavily on predefined rules and templates.&lt;/p&gt;

&lt;p&gt;Broadly speaking, AI in games falls into two camps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI that runs during gameplay&lt;/strong&gt;, handling moment-to-moment decision making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI that assists development&lt;/strong&gt;, generating assets, levels, or tools behind the scenes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second category is less visible to players, but it's been quietly growing for years. Developers use AI-assisted tools for everything from mesh generation and texture synthesis to complex animation pipelines - supported by inverse kinematics and motion-capture clean-up.&lt;/p&gt;

&lt;p&gt;Machine-assisted authoring isn't new, but the explosion of modern generative AI has amplified it. The biggest question now is how to use these tools responsibly and effectively without breaking the systems and workflows that developers rely on.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wildermyth and “structured randomness”
&lt;/h2&gt;

&lt;p&gt;One of my favourite reference points for procedural storytelling is &lt;em&gt;&lt;a href="https://wildermyth.com/" rel="noopener noreferrer"&gt;Wildermyth&lt;/a&gt;&lt;/em&gt; by Worldwalker Games. It's a tactical RPG that leans heavily on procedural generation to create bespoke stories and characters that evolve across a campaign.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wildermyth&lt;/em&gt; mixes handcrafted narrative arcs with procedural character traits, relationships, and emergent events. The team published a helpful explanation of their templating system, which outlines how stories are assembled around randomised components: &lt;a href="https://wildermyth.com/wiki/Generic_Campaign" rel="noopener noreferrer"&gt;Wildermyth generic campaign&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The effect is pleasingly coherent. Characters age, form relationships, suffer injuries, retire and die. And the game doesn't just track those changes, it weaves them back into the story. A character who retired in one game might pop up as a wild old sage in the next. Your choices influence how events unfold, but the underlying system is still largely randomised. It's a system that feels hand authored, but isn't.&lt;/p&gt;

&lt;p&gt;While Wildermyth is impressive, but it also exposes the limits of procedural storytelling.&lt;br&gt;
Because no matter how clever your templates are, there's a ceiling to its playability.&lt;br&gt;
Procedural generation is deterministic and rule-bound. It can remix, but it can't invent. Eventually, the system starts to repeat itself. As a player, you'll notice the seams and the repetition.&lt;/p&gt;

&lt;p&gt;This is where modern generative AI becomes interesting: instead of relying solely on predefined templates, models can fill in gaps, reinterpret context, or expand narrative branches dynamically, giving you a more open-ended storytelling system without losing coherence entirely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Modern generative AI
&lt;/h2&gt;

&lt;p&gt;When people talk about “AI” today, they're usually referring to two types of models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Large Language Models (LLMs)&lt;/strong&gt;, such as GPT-style transformers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diffusion models&lt;/strong&gt; for image generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These models are trained on large datasets and learn statistical patterns in text or imagery. Once trained, they can generate new content in the style of what they've seen.&lt;/p&gt;

&lt;p&gt;While people often focus on the big models, smaller and more specialised models are increasingly important. Codex for code generation, Stable Diffusion for images, and small &lt;a href="https://www.llama.com/" rel="noopener noreferrer"&gt;LLaMA-class models&lt;/a&gt; for focused tasks are all good examples.&lt;/p&gt;

&lt;p&gt;Using these models in real systems introduces a different style of programming. Instead of writing deterministic functions, we wrap models with validation and guardrails to coerce their unpredictable outputs into structured formats. For game development, that often means combining generative systems with the kind of tooling we already have for procedural content.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building a Wildermyth-like system with AI
&lt;/h2&gt;

&lt;p&gt;Inspired by Wildermyth, I started sketching out a system that blended procedural generation with AI-authored content. The goal was not to let the model run wild, but to use it to fill narrative space within a controlled structure.&lt;/p&gt;

&lt;p&gt;The idea was to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model a world using data structures for narrative elements, character traits, histories and events.&lt;/li&gt;
&lt;li&gt;Describe RPG-style dialogue, relationships, and interactions with TypeScript types.&lt;/li&gt;
&lt;li&gt;Use concepts from &lt;a href="https://martinfowler.com/eaaDev/EventSourcing.html" rel="noopener noreferrer"&gt;EventSourcing&lt;/a&gt; to store world histories, character actions, and place-based records.&lt;/li&gt;
&lt;li&gt;Let the LLM generate content within these rails, keeping everything internally consistent.&lt;/li&gt;
&lt;li&gt;Pre-generate thousands of possible universes, settings, cities, and characters using automated scripts.&lt;/li&gt;
&lt;li&gt;Mix pre-generated content with on-the-fly generation to build a sort of “infinite role-playing game”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using generative AI in this way feels more like an evolution of procedural generation rather than an entirely new system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Taming the randomness
&lt;/h2&gt;

&lt;p&gt;Working with LLMs introduces three major challenges:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Context window limitations
&lt;/h3&gt;

&lt;p&gt;Models need enough world information to stay grounded, but you can't exceed their &lt;a href="https://www.ibm.com/think/topics/context-window" rel="noopener noreferrer"&gt;context window&lt;/a&gt; - the amount of text that they can process at once. Even large windows are easy to fill when you're generating worlds, cities, and characters.&lt;/p&gt;

&lt;p&gt;The solution is a &lt;strong&gt;multi-pass hierarchy&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate high-level universe types.&lt;/li&gt;
&lt;li&gt;Use universe types to generate world summaries and histories.&lt;/li&gt;
&lt;li&gt;Generate cities using only world-level summaries.&lt;/li&gt;
&lt;li&gt;Generate characters using city context, world summaries, and a short universe description.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each level is fed only the context it needs. Moving up and down the hierarchy keeps prompts short but coherent.&lt;/p&gt;
&lt;h4&gt;
  
  
  The structure of a universe
&lt;/h4&gt;

&lt;p&gt;Using types to define the structure of generated content helps keep things consistent. Here's an example of a &lt;code&gt;Universe&lt;/code&gt; type that guides high-level world generation. You can be as specific or vague as you like with the parameters, allowing for a wide variety of generated settings and themes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Universe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;preferredGenre&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// eg "fantasy", "sci-fi", "post-apocalyptic"&lt;/span&gt;
  &lt;span class="nx"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// eg "hopeful", "grimdark", "mythic"&lt;/span&gt;
  &lt;span class="nx"&gt;scaleHint&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// eg "single planet", "system", "galaxy"&lt;/span&gt;
  &lt;span class="nx"&gt;techOrMagicBias&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// eg "high fantasy", "steampunk gadgets"&lt;/span&gt;
  &lt;span class="nx"&gt;factionCount&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Desired number of factions (cap at 6 in output)&lt;/span&gt;
  &lt;span class="nx"&gt;historyLengthHint&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// eg "hundreds of years", "millennia"&lt;/span&gt;
  &lt;span class="nx"&gt;themesAndAestheticsBias&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// Vibe/style hints, eg ["solarpunk", "gothic"]&lt;/span&gt;
  &lt;span class="nx"&gt;randomSeed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Deterministic seed for proc-gen&lt;/span&gt;
  &lt;span class="nx"&gt;namingConventions&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Naming preferences for generated proper nouns&lt;/span&gt;
    &lt;span class="nx"&gt;planetStyle&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;countryStyle&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cityStyle&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;factionStyle&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&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 you can prompt the model to fill in the details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**You are a worldbuilder AI for a proc-gen + gen-AI assisted RPG.**&lt;/span&gt;

Accept a &lt;span class="gs"&gt;**seed JSON**&lt;/span&gt; and return &lt;span class="gs"&gt;**only**&lt;/span&gt; a JSON document describing an internally-consistent universe outline to ground subsequent generation runs.

&lt;span class="gu"&gt;## Requirements&lt;/span&gt;

When generating a universe, include:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**universeType**&lt;/span&gt;: The genre or type (fantasy, sci-fi, post-apocalyptic, etc.).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**constraints**&lt;/span&gt;: Rules/limitations (e.g., magic rules, physics constraints, aesthetics).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**scale**&lt;/span&gt;: Description of scope (single planet, system, galaxy).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**topology**&lt;/span&gt;: List of locations with names and relationships (planets, countries, cities).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**universeAge**&lt;/span&gt;: Timeline length (hundreds, thousands, etc.).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**historyEvents**&lt;/span&gt;: Up to 10 major events shaping the world, each with a &lt;span class="sb"&gt;`year`&lt;/span&gt; (absolute, relative, or approximate) and &lt;span class="sb"&gt;`description`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**politicalContext**&lt;/span&gt;: Current political situation.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**factions**&lt;/span&gt;: Up to 6 factions, each with name, description, goals, relationships.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**technologyOrMagicLevel**&lt;/span&gt;: Description of available tech/magic, everyday use, limits.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**themesAndAesthetics**&lt;/span&gt;: Short descriptors of the universe's tone, look, or feel (e.g. _dieselpunk decay_, _solarpunk optimism_, _dark gothic mysticism_).

&lt;span class="gu"&gt;## Behavior Rules&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Input:**&lt;/span&gt; A seed JSON (schema below). Missing fields → randomize sensibly.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Output:**&lt;/span&gt; &lt;span class="gs"&gt;**JSON only**&lt;/span&gt; (no prose, no markdown) conforming to the Output Schema.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**IDs:**&lt;/span&gt; Generate &lt;span class="gs"&gt;**UUIDv4 (lowercase)**&lt;/span&gt; for every entity (&lt;span class="sb"&gt;`universeId`&lt;/span&gt;, planets, countries, cities, factions).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Relationships:**&lt;/span&gt; Use &lt;span class="gs"&gt;**ID-based**&lt;/span&gt; relationships only (&lt;span class="sb"&gt;`targetId`&lt;/span&gt;, &lt;span class="sb"&gt;`relatedIds`&lt;/span&gt;). No name references.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Internal Consistency:**&lt;/span&gt; Every referenced &lt;span class="sb"&gt;`targetId`&lt;/span&gt;/&lt;span class="sb"&gt;`relatedId`&lt;/span&gt; must exist. Names unique within scope.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Cardinality Caps:**&lt;/span&gt; ≤ 12 planets; ≤ 24 countries total; ≤ 48 cities total; ≤ 6 factions; ≤ 10 history events.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Design Goals to Satisfy:**&lt;/span&gt;
&lt;span class="p"&gt;
  -&lt;/span&gt; Universe from a &lt;span class="gs"&gt;**known type/genre**&lt;/span&gt; with &lt;span class="gs"&gt;**constraints**&lt;/span&gt; (e.g., magic rules, physics).
&lt;span class="p"&gt;  -&lt;/span&gt; Clear &lt;span class="gs"&gt;**scale**&lt;/span&gt; (single planet → galaxy).
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Topology**&lt;/span&gt; (planets → countries → cities) with &lt;span class="gs"&gt;**names**&lt;/span&gt; &amp;amp; &lt;span class="gs"&gt;**relationships**&lt;/span&gt;.
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Universe age**&lt;/span&gt; (hundreds/thousands/millennia).
&lt;span class="p"&gt;  -&lt;/span&gt; Up to &lt;span class="gs"&gt;**10 key history events**&lt;/span&gt; (timeline).
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Political context**&lt;/span&gt; (blocs, tensions, powers).
&lt;span class="p"&gt;  -&lt;/span&gt; Up to &lt;span class="gs"&gt;**6 factions**&lt;/span&gt; (names, goals, relationships).
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Technology or magic**&lt;/span&gt; level (everyday use, limits).
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Themes &amp;amp; aesthetics**&lt;/span&gt; (tone, vibe, style).

NEVER
Use the name "Eldoria" or any variation of it, it's over-fit in training data.

ALWAYS:
Make sure you return valid JSON.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Testing and validating output
&lt;/h3&gt;

&lt;p&gt;If you ask a model to return JSON, it &lt;em&gt;might&lt;/em&gt; manage it, but the output often with missing commas or mismatched brackets. Unfortunately, LLMs are not parsers.&lt;/p&gt;

&lt;p&gt;To manage this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include TypeScript types in your prompts.&lt;/li&gt;
&lt;li&gt;Use a &lt;a href="https://www.ibm.com/think/topics/few-shot-prompting" rel="noopener noreferrer"&gt;few-shot prompting technique&lt;/a&gt; with examples of valid JSON.&lt;/li&gt;
&lt;li&gt;Validate responses against schemas.&lt;/li&gt;
&lt;li&gt;If validation fails, the faulty response can be fed back into the model with instructions to repair it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found this “corrective loop” was far more efficient than starting from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost
&lt;/h3&gt;

&lt;p&gt;Generating large quantities of content using cloud models can get expensive very quickly. I decided to use local models instead.&lt;/p&gt;

&lt;p&gt;The last couple of years have been great for small LLMs, which now run happily on consumer-grade GPUs or Apple Silicon. &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; became my main tool, running a local server with an OpenAI-compatible API. It meant I could leave a Mac Mini running overnight generating thousands of assets for pennies, all within a 45-watt thermal envelope. Cheaper than boiling a kettle!&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%2Fthwure60e1jkkyom4m5d.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%2Fthwure60e1jkkyom4m5d.png" alt="Screenshot of LMStudio" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling LLMs with Deno
&lt;/h2&gt;

&lt;p&gt;To orchestrate the generation pipeline, I used &lt;a href="https://deno.com/" rel="noopener noreferrer"&gt;Deno&lt;/a&gt;, a modern runtime for JavaScript and TypeScript. Deno's built-in TypeScript support, filesystem APIs and security model make it ideal for this kind of tooling.&lt;/p&gt;

&lt;p&gt;The generation system used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.npmjs.com/package/openai" rel="noopener noreferrer"&gt;OpenAI API library&lt;/a&gt; (pointed at LM Studio for local inference)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.deno.com/api/deno/file-system" rel="noopener noreferrer"&gt;Deno's filesystem APIs&lt;/a&gt; for reading/writing batches of generated content&lt;/li&gt;
&lt;li&gt;A small command-line runner to handle context management, multi-pass generation, and retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a simplified version of the LLM client I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm:openai/client&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;class&lt;/span&gt; &lt;span class="nc"&gt;LocalLlmClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk-no-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:1234/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// LM Studio local endpoint&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/gpt-oss-20b&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;async&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You're a content generator for narrative stories. You're trying to make
                   original, creative, and interesting content, worlds, and characters. You
                   respond in perfectly formed JSON whenever requested.&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script orchestrated universe → world → city → character → dialogue generation using the structured multi-pass approach described earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the conversation system
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to test a narrative system is to prototype the conversation layer. I wanted something that felt like a classic &lt;a href="https://www.youtube.com/watch?v=rEbr2f3-F2A" rel="noopener noreferrer"&gt;“Bioware NPC conversation”&lt;/a&gt;, with a branching dialogue structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversations are represented as dialogue trees. Each node on the tree contains text, conditions, outcomes, and links to other nodes.&lt;/li&gt;
&lt;li&gt;Nodes can modify sentiment values, or unlock knowledge tags, and could be expanded later to trigger quests or events or drop items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built a small React app and loaded a random pre-generated conversation from disk through a simple API.&lt;/p&gt;

&lt;p&gt;The API was built with Hono and ran with Deno Serve, making it lightweight and easy to deploy locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hono&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;cors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hono/cors&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;universeFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readDirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../author/output/&lt;/span&gt;&lt;span class="dl"&gt;"&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;f&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;f&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="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_universe.json&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;app&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;Hono&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/random&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/random&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&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;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;universeFiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;universeFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;universe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../author/output/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_universe.json`&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;dialogue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../author/output/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_dialogue.json`&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;flavour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../author/output/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_flavour.json`&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;universe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;flavour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dialogue&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic UI allowed me to step through the dialogue tree, making choices and seeing how the conversation evolved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Minimal React demo wired to the Hono /random endpoint&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Sentiment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;minSentiment&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Sentiment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSentiment&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Sentiment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flagsAll&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="nl"&gt;flagsAny&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Effects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sentimentDelta&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Sentiment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;setFlags&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="nl"&gt;clearFlags&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reveal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conflict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leave&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reward&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DialogueOption&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&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="nl"&gt;npcResponse&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="nl"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Effects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DialogueNode&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&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="nl"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DialogueOption&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DialogueTree&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&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="nl"&gt;npcName&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="nl"&gt;startNodeId&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="nl"&gt;initialSentiment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sentiment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;DialogueNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StoryData&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;universe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flavour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flavourText&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DialogueTree&lt;/span&gt;&lt;span class="p"&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;function&lt;/span&gt; &lt;span class="nf"&gt;ConversationDemo&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StoryData&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSentiment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Sentiment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFlags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEnded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLog&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;load&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8000/random&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="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StoryData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startNodeId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setSentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialSentiment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nf"&gt;setEnded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flavour&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;flavourText&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flavour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flavourText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minSentiment&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minSentiment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxSentiment&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxSentiment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagsAll&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagsAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&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;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagsAny&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagsAny&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&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;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;choose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DialogueOption&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// apply effects&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;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;sentimentDelta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setSentiment&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&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;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sentimentDelta&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;setFlags&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setFlags&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setFlags&lt;/span&gt;&lt;span class="o"&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;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;clearFlags&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setFlags&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="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;next&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;Set&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;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearFlags&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&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;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&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;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// echo player's choice and NPC response&lt;/span&gt;
    &lt;span class="nf"&gt;setLog&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&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;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`You: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npcName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npcResponse&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setCurrentId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&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;nextNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&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;nextNode&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nextNode&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setEnded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setEnded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentId&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;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&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;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system-ui, sans-serif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; — &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npcName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12px&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&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#111&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;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;#eee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;marginBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npcName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ended&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;listStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;opt&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;textAlign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10px 12px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12px 0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Conversation ended.
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Start another&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sentiment: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sentiment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup made it easy to iterate on the feel of the conversations before committing to building an entire game.&lt;/p&gt;




&lt;h2&gt;
  
  
  Some fun extras
&lt;/h2&gt;

&lt;p&gt;To make the prototype feel more game-like, I added a couple of small procedural touches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Character avatars
&lt;/h3&gt;

&lt;p&gt;Each NPC was rendered using seeded procedural SVGs. Different shapes, colours, and layers combined to give characters a distinct look determined entirely by their name and role. Universes themed around sci-fi, fantasy, or modern settings got different palettes or accessories. If an NPC disliked you, their eyebrows and expressions shifted accordingly.&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%2Fhd22g0zk3wy0yd03og8d.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%2Fhd22g0zk3wy0yd03og8d.png" alt="Generated avatars" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  World maps
&lt;/h3&gt;

&lt;p&gt;The worlds themselves used a tiny cellular automata system. A miniature run of Conway's Game of Life generated landmasses. Cities were placed according to simple rules. The map was then blurred and tinted to make it look like a globe or parchment map depending on the universe type.&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%2F77ju0csm9vg9xpxrwfgp.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%2F77ju0csm9vg9xpxrwfgp.png" alt="Generated maps" width="800" height="876"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key point: everything was &lt;strong&gt;seeded&lt;/strong&gt;, so the same input always produced the same output. Consistency is crucial when you want a coherent game world.&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%2Fl22orsjysbeadzpfkro1.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%2Fl22orsjysbeadzpfkro1.png" alt="A screenshot of the finished game" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Applications outside games
&lt;/h2&gt;

&lt;p&gt;The techniques discussed here aren't limited to games development. Wrapping non-deterministic model outputs in structured systems has wider use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Systems where outputs must conform to strict data types, such as APIs or data pipelines.&lt;/li&gt;
&lt;li&gt;Workflows where known data sources must be preserved while adding human-like variation, like content personalization.&lt;/li&gt;
&lt;li&gt;Processes that benefit from some imaginative expansion while staying rooted in constraints, such as automated report generation or creative writing aids.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Games are just particularly good testing ground. They've always balanced authored content, procedural randomness, and systemic behaviour. Generative AI is simply another tool that can sit alongside our other tools, provided it's wrapped in the right guardrails.&lt;/p&gt;

&lt;p&gt;If anything, the future will probably look like a blend of human authorship, procedural logic, and model-assisted generation. Not to replace writers or designers, but to allow small teams to produce richer worlds, in the same way that previous generations of tooling levelled the playing field in the 90s and 2000s.&lt;/p&gt;

&lt;p&gt;The challenge is keeping human creativity at the centre. The technology should extend what's possible, not erase the artistry and human element that makes games enjoyable in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLMs obviously still have their flaws
&lt;/h2&gt;

&lt;p&gt;You may have noticed in the earlier prompt there was a rule to &lt;code&gt;NEVER&lt;br&gt;
Use the name "Eldoria" or any variation of it, it's over-fit in training data.&lt;/code&gt; This is because if you ask any of the most popular LLMs to create and describe a word, it has an over-eagerness to tell you about "Eldoria". When I was generating worlds I found Eldoria repeated often in the data. Try it out yourself in an online LLM, you may find you also get this name or something similar, in fact there is an entire &lt;a href="https://www.reddit.com/r/ChatGPT/comments/1bb2mzm/what_is_ais_obsession_with_eldoria" rel="noopener noreferrer"&gt;reddit conversation&lt;/a&gt; where others have found the same thing. Perhaps one day we will discover Eldoria, the land where AI dreams to be.&lt;/p&gt;

&lt;p&gt;I fell foul of plenty of issues with JSON repair loops failing, generated dialog that went nowhere or confused the speakers, and occasionally contradicting characters and world outlines. LLMs are of course no match, nor replacement for human writers and creatives, but there is still plenty of room for improvement of the prompts and for a human editor to come in and make something from the thousands of generated concepts. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let me know what you think
&lt;/h2&gt;

&lt;p&gt;All in all, this was an interesting experiment to see how far a single developer could push narrative generation with a hybrid deterministic and generative pipeline and I'm excited to work on it more.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far! I hope this peek into my experiments helps spark your own ideas about blending procedural generation with modern LLMs. If you’ve been working with similar systems or have your own lessons learned, I’d love to hear about them! Drop me a comment down below or drop me a message, I'm &lt;a class="mentioned-user" href="https://dev.to/thisisjofrank"&gt;@thisisjofrank&lt;/a&gt; on all the socials.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>gamedev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to convert CommonJS to ESM</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Tue, 22 Oct 2024 17:46:36 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/how-to-convert-commonjs-to-esm-1jc0</link>
      <guid>https://dev.to/thisisjofrank/how-to-convert-commonjs-to-esm-1jc0</guid>
      <description>&lt;p&gt;ECMAScript modules (”ESM”) are the official, modern way of writing and sharing JavaScript — it’s supported in many environments (e.g. browsers, the edge, and modern runtimes like &lt;a href="https://deno.com" rel="noopener noreferrer"&gt;Deno&lt;/a&gt;, and offers a better development experience (e.g. async loading and being able to export without globals). While CommonJS was the standard for many years, supporting &lt;a href="https://dev.to/blog/commonjs-is-hurting-javascript"&gt;CommonJS today is hurting the JavaScript community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All new JavaScript should be written in ESM for future proofing. However, there are many cases where a legacy code base needs to be modernized for compatibility reasons with newer packages. In this blog post, we’ll show you how to &lt;a href="https://docs.deno.com/runtime/tutorials/cjs_to_esm/" rel="noopener noreferrer"&gt;migrate the syntax of a legacy CommonJS project to one that supports ESM&lt;/a&gt; and tools to help smooth out that process.&lt;/p&gt;

&lt;h1&gt;
  
  
  In this document
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Module imports and exports&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Other changes&lt;/li&gt;
&lt;li&gt;Tools for migrating&lt;/li&gt;
&lt;li&gt;What's next&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Module imports and exports
&lt;/h1&gt;

&lt;p&gt;Here’s how you can update import and export syntax from CommonJS to ESM.&lt;/p&gt;

&lt;p&gt;On the export side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- function addNumbers(num1, num2) {
&lt;/span&gt;&lt;span class="gi"&gt;+ export function addNumbers(num1, num2) {
&lt;/span&gt;  return num1 + num2;
};
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- module.exports = {
-   addNumbers,
- }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the import side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- const { addNumbers } = require("./add_numbers");
&lt;/span&gt;&lt;span class="gi"&gt;+ import { addNumbers } from "./add_numbers.js");
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;console.log(addNumbers(2, 2));
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in ESM, the file extension must be included in the module path. Fully specified imports reduce ambiguity by ensuring the correct file is always imported by the module resolution process. Plus, it aligns with how browsers handle module imports, making it easier to write isomorphic code that’s predictable and maintainable.&lt;/p&gt;

&lt;p&gt;What about conditional imports? If you are using Node.js v14.8 or later (or Deno), then you’ll have access to top-level await, which you can use to make &lt;code&gt;import&lt;/code&gt; synchronous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- const module = boolean ? require("module1") : require("module2");
&lt;/span&gt;&lt;span class="gi"&gt;+ const module = await (boolean ? import("module1") : import("module2"));
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Update &lt;code&gt;package.json&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;If you’re using &lt;code&gt;package.json&lt;/code&gt;, you’ll need to make a few adjustments to support ESM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
  "name": "my-project",
&lt;span class="gi"&gt;+ "type": "module",
&lt;/span&gt;&lt;span class="gd"&gt;- "main": "index.js",
&lt;/span&gt;&lt;span class="gi"&gt;+ "exports": "./index.js",
&lt;/span&gt;  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the leading &lt;code&gt;"./"&lt;/code&gt; in ESM is necessary as every reference has to use the full pathname, including directory and file extension.&lt;/p&gt;

&lt;p&gt;Also, both &lt;code&gt;"main"&lt;/code&gt; and &lt;code&gt;"exports"&lt;/code&gt; define entry points for a project. However, &lt;code&gt;"exports"&lt;/code&gt; is a modern alternative to &lt;code&gt;"main"&lt;/code&gt; in that it gives authors the ability to clearly define the public interface for their package by allowing multiple entry points, supporting conditional entry resolution between environments, and preventing other entry points outside of those defined in &lt;code&gt;"exports"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./other"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./other.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, another way to tell Node to run the file in ESM is to use the &lt;code&gt;.mjs&lt;/code&gt; file extension. This is great if you want to update a single file to ESM. But if your goal is to convert your entire code base, it’s easier to update the &lt;code&gt;type&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Other changes
&lt;/h1&gt;

&lt;p&gt;Since JavaScript inside an ESM will automatically run in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode" rel="noopener noreferrer"&gt;strict mode&lt;/a&gt;, you can remove all instances of &lt;code&gt;"use strict";&lt;/code&gt; from your code base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- "use strict";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CommonJS also supported a handful of built-in globals that do not exist in ESM, such as &lt;code&gt;__dirname&lt;/code&gt; and &lt;code&gt;__filename&lt;/code&gt;. One simple way to get around that is to use a quick shim to populate those values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node 20.11.0+, Deno 1.40.0+&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&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;__filename&lt;/span&gt; &lt;span class="o"&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Previously&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__dirname&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;fileURLToPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:url&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;__filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fileURLToPath&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Tools for migrating
&lt;/h1&gt;

&lt;p&gt;While the above touches upon the changes necessary to convert a CommonJS code base to an ESM one, there are a few tools to help with that transition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;, you can quickly convert all import and export statements from CommonJS to ESM&lt;/strong&gt;. Simply hover over the &lt;code&gt;require&lt;/code&gt; keyword, hit “quick fix”, and all of those statements in that file will be updated to ESM:&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%2Fkii8qmlx6jv874jqv1xi.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%2Fkii8qmlx6jv874jqv1xi.gif" alt="VSCode offers a quick fix to converting CommonJS requires to ESM imports" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll notice that VSCode can swap out the proper keywords for importing and exporting, but the specifiers are missing filename extensions. If you have Deno installed, you can quickly add them by running &lt;a href="https://docs.deno.com/runtime/manual/tools/linter/" rel="noopener noreferrer"&gt;&lt;code&gt;deno lint --fix&lt;/code&gt;&lt;/a&gt;. Deno’s linter comes with a &lt;code&gt;no-sloppy-imports&lt;/code&gt; rule that will show a linting error when an import path doesn’t contain the file extension.&lt;/p&gt;

&lt;p&gt;For a more end-to-end approach to converting CommonJS to ESM, there are a few transpilation options. There are npm packages &lt;a href="https://www.npmjs.com/package/cjs2esm" rel="noopener noreferrer"&gt;cjs2esm&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/cjstoesm" rel="noopener noreferrer"&gt;cjstoesm&lt;/a&gt;, as well as the Babel plugin &lt;a href="https://github.com/tbranyen/babel-plugin-transform-commonjs" rel="noopener noreferrer"&gt;babel-plugin-transform-commonjs&lt;/a&gt;. However, these tools may not be actively maintained and are not feature complete, so keep that in mind when evaluating them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next
&lt;/h1&gt;

&lt;p&gt;ESM is the standard JavaScript way to share code and all new JavaScript should support it. Choosing to support CommonJS today can be extremely painful for module authors and developers who don’t want to troubleshoot legacy compatibility issues. &lt;a href="https://jsr.io/" rel="noopener noreferrer"&gt;JSR&lt;/a&gt; - the open source, modern JavaScript registry, explicitly forbids modules using CommonJS. Everyone can do their part in levelling up the JavaScript ecosystem!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Gentle Intro to TypeScript</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Thu, 11 Jul 2024 10:55:33 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/a-gentle-intro-to-typescript-3b54</link>
      <guid>https://dev.to/thisisjofrank/a-gentle-intro-to-typescript-3b54</guid>
      <description>&lt;p&gt;For many who came to programming via JavaScript it is easy to fall in love with its low barrier to entry and versatile nature. JavaScript runs in a browser, can be written in notepad, is interpreted line by line and requires no complicated compilation or tooling. JavaScript has democratized software development by allowing developers from all backgrounds to pick it up and start coding. But with Javascript’s forgiving nature, comes increased chances to make mistakes and create bugs.&lt;/p&gt;

&lt;p&gt;Consider this JavaScript program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple addition program that is designed to take two numbers and add them together, returning the result. Unexpected, often unpredictable things can start happening when we call this function with values that aren’t numbers.&lt;/p&gt;

&lt;p&gt;We only really want to call this function with numbers - it doesn't make sense otherwise - and in fact, if you pass values to it that are not numbers, you'll end up with often bizarre and unpredictable results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 12&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 1hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript is a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Dynamic_typing" rel="noopener noreferrer"&gt;dynamically typed&lt;/a&gt; language. This means that the types of data that we store in our variables can change at runtime. This can make it complicated to capture our intention in our JavaScript code - that our add function should only be called with numbers.&lt;/p&gt;

&lt;p&gt;What we are seeing happen here is called &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion" rel="noopener noreferrer"&gt;type coercion&lt;/a&gt; - JavaScript automatically converts values from one data type to another. This can happen explicitly, through the use of functions and operators, or implicitly, when JavaScript expects a certain type of value in a particular context. Implicit type coercion can sometimes lead to unexpected results, especially in complex expressions. Here are a few cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "12" (number 1 is converted to string)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "21" (number 1 is converted to string)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4 (string "5" is converted to number)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10 (both strings are converted to numbers)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true (number 0 is converted to false)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false (no type coercion, different types)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confusing right?!&lt;/p&gt;

&lt;p&gt;In a JavaScript codebase, the best we can do is add some guard checks to our program that will throw errors if invalid values are provided. The problem with having only these runtime checks to protect us from errors is that we'll often find out when a bug has been introduced into our code at the same time that our users do. So how do we protect ourselves from this often confusing JavaScript behavior?&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript to the Rescue
&lt;/h2&gt;

&lt;p&gt;TypeScript can help us to describe the intention of our code by being explicit about what types we expect where. TypeScript is a superset of JavaScript that adds additional type information to the language. When you compile TypeScript, JavaScript is produced that can run anywhere, so it's really easy to incrementally use it to make your development experience better without having to rebuild all of your software.&lt;/p&gt;

&lt;p&gt;Types allow you to catch errors in your code before it runs. If you accidentally assign a value of the wrong type to a variable, you’ll get a compile error. Types also make your code easier to read because you can explicitly state what kind of value you expect. We can also use tools to make types even more powerful. Code editors and IDEs have inbuilt tools to help autocomplete your code as you write when you’re using types correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do we add type annotations?
&lt;/h3&gt;

&lt;p&gt;You add a type annotation in TypeScript by appending a colon (&lt;code&gt;:&lt;/code&gt;) followed by the desired type after the function parameter's name. If we were to extend our previous &lt;code&gt;add&lt;/code&gt; example to convert it to TypeScript it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the simplest change we can make to our code to make it more robust. Now the compiler knows that any code that attempts to call &lt;code&gt;add()&lt;/code&gt; with anything but two numbers will fail at runtime, so it will throw an error upon compilation to tell you your program isn't valid.&lt;/p&gt;

&lt;p&gt;If we try and call add with a number and a string, for example, we’ll get a compiler error:&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%2Fdeno.com%2Fblog%2Fdeno-bites%2Fts-intro-screenshot.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%2Fdeno.com%2Fblog%2Fdeno-bites%2Fts-intro-screenshot.png" title="error when calling add with a string" alt="compiler error in VSCode" width="703" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TypeScript is smart enough to work out some of the types in your program for you based on the information you've given it, we call this "type inference". If we take the above example and expand it out, adding all the type annotations that we can, it'd look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we've explicitly added annotations for the function parameters, a return type of the &lt;code&gt;add&lt;/code&gt; function, and the variable &lt;code&gt;result&lt;/code&gt;. As a general rule, developers add "just enough" type annotations to tell the TypeScript compiler what's going on, and let it infer the rest. In our original example, by adding type annotations to the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; parameters, the TypeScript compiler could inspect the code and realize we only ever add two numbers, so would infer both the function return type, and the type of the variable result to be of type &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even if you only ever use TypeScript to annotate function parameters you'll immediately remove an entire category of errors from your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning your TypeScript back into JavaScript
&lt;/h2&gt;

&lt;p&gt;If your project is built using Node, you will need to add the &lt;a href="https://www.npmjs.com/package/typescript" rel="noopener noreferrer"&gt;&lt;code&gt;typescript&lt;/code&gt;&lt;/a&gt; package and run the &lt;code&gt;tsc&lt;/code&gt; compiler tool. We’ve written an &lt;a href="https://deno.com/blog/intro-to-tsconfig" rel="noopener noreferrer"&gt;introduction to configuring your TypeScript compiler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deno comes with the TypeScript compiler built right in, so if you're using Deno, you do not need any other configuration or tools. &lt;a href="https://docs.deno.com/runtime/manual/advanced/typescript/overview" rel="noopener noreferrer"&gt;Deno supports TypeScript out of the box&lt;/a&gt;, automatically turning TypeScript into JavaScript as we execute your source code.&lt;/p&gt;

&lt;p&gt;On the client side, you can use &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, a tool after our own heart, which does a similar transparent compilation for you, so if you're a front end developer, you can still get TypeScript joy in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;In our next Deno Bite we'll talk about common types that you’ll need in your TS code and how to use them to build more complex types to make your code clear and bug-free!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are there any topics on TypeScript you would like me to cover? Let me know in the comments or on &lt;a href="https://twitter.com/deno_land" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://discord.gg/deno" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>deno</category>
    </item>
    <item>
      <title>How to document your JavaScript package</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Fri, 17 May 2024 16:22:09 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/how-to-document-your-javascript-package-2ngf</link>
      <guid>https://dev.to/thisisjofrank/how-to-document-your-javascript-package-2ngf</guid>
      <description>&lt;p&gt;Creating and publishing open source packages is a great way to contribute to the ecosystem and community. You made something cool and want people to use it. But simply publishing your module to a registry and crossing your fingers won't get users. Helping your users become successful with your package means not only writing concise, descriptive documentation, but also ensuring your users can access the documentation within their workflows (e.g. in VSCode) to save them time.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://jsdoc.app/" rel="noopener noreferrer"&gt;JSDoc&lt;/a&gt; it's easy to write documentation that is coupled with your code and can be consumed by users in a variety of formats. When combined with a modern publishing flow like &lt;a href="https://jsr.io" rel="noopener noreferrer"&gt;JSR&lt;/a&gt;, you can easily create comprehensive documentation for your package that not only fits within your workflow, but also integrates directly in the tools your users consume your package with. This blog post aims to cover best practices when writing JSDoc-style comments to get your users up and running as quickly as possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why JSDoc?&lt;/li&gt;
&lt;li&gt;A brief intro to JSDoc&lt;/li&gt;
&lt;li&gt;Provide good type information&lt;/li&gt;
&lt;li&gt;Tags, tags, tags&lt;/li&gt;
&lt;li&gt;Add examples&lt;/li&gt;
&lt;li&gt;But what should I document?&lt;/li&gt;
&lt;li&gt;Use markdown&lt;/li&gt;
&lt;li&gt;Link internally&lt;/li&gt;
&lt;li&gt;Keep JSDoc up-to-date&lt;/li&gt;
&lt;li&gt;Audit your JSDoc&lt;/li&gt;
&lt;li&gt;What's next&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why JSDoc?
&lt;/h2&gt;

&lt;p&gt;While a good README answers "&lt;em&gt;why&lt;/em&gt; should I use your package?", good documentation should answer "&lt;em&gt;how&lt;/em&gt; can I use your package?". Users browsing your documentation have a problem they need to solve, and your documentation should provide them with the answer in the fewest clicks and keyboard taps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jsdoc.app/" rel="noopener noreferrer"&gt;JSDoc&lt;/a&gt; is a great way to write reference documentation that is coupled with the code itself and can be consumed by users in a variety of formats, such as HTML, markdown, JSON, or in their IDE or text editor. Here is a quick diagram of an example JSDoc-style comment and how it appears as documentation in various mediums:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fjsdoc-diagram.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fjsdoc-diagram.webp" alt="JSDoc diagram on JSR" width="800" height="1980"&gt;&lt;/a&gt;&lt;/p&gt;

When you write JSDoc-style comments in your code and publish to JSR, it will appear formatted on your package's documentation page on JSR, VSCode tooltips and auto-complete, and in `deno doc` output. 



&lt;p&gt;Writing good JSDoc can improve the success of your package. Before we dive into some best practices, here's a brief high-level introduction to JSDoc.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief intro to JSDoc
&lt;/h2&gt;

&lt;p&gt;JSDoc turns your comments in your code into a documentation object that can be rendered and displayed in a variety of formats.&lt;/p&gt;

&lt;p&gt;JSDoc comments are any block comments that begin with &lt;code&gt;/**&lt;/code&gt; and end with &lt;code&gt;*/&lt;/code&gt; that precede a block of code. Here's an example:&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="cm"&gt;/** Adds two values and returns the sum. */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value2&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="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value2&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;This JSDoc will then appear as a tooltip in your IDE:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fexample-sum-1.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fexample-sum-1.webp" alt="Example of JSDoc with sum function" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSDoc comments can span multiple lines. Each line should start with &lt;code&gt;*&lt;/code&gt; and should be indented by one space.&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="cm"&gt;/**
 * Adds two values and returns the sum.
 *
 * NOTE: JavaScript math uses IEEE 754 floating point arithmetic, so there may
 * be some rounding errors when adding two numbers.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value2&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="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value2&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;&lt;strong&gt;The first paragraph of a JSDoc comment is the most important.&lt;/strong&gt; It is a summary of the symbol and is shown in tooltips, auto-completions in your editor, and is indexed by search. The first paragraph should be a concise description of the symbol, and should be written in a way that helps users quickly understand what this function does. &lt;br&gt;
For example, don't write:&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="cm"&gt;/**
 * This function takes a string in the first and returns a string. It looks for
 * all the spaces in the input string using a regexp, and then replaces them one
 * by one with an underscore. The function then returns the modified string.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;replaceSpacesWithUnderscores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/ /g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, concisely describe what the function does:&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="cm"&gt;/**
 * Replaces all spaces in a string with underscores.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;replaceSpacesWithUnderscores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/ /g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional information like the implementation details, caveats, or examples should be added in subsequent paragraphs. Because JSDoc supports markdown, you can even use headings to separate different sections.&lt;/p&gt;

&lt;p&gt;Simple and concise summaries help users quickly filter through a list of symbols during auto-complete and find the one they need. Once they've found the symbol, they can read through the rest to learn about the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide good type information
&lt;/h2&gt;

&lt;p&gt;After the succinct descriptive summary, it's important to provide good type information for the symbols you are exposing in your package. This serves two main purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It allows auto-completion on parameters and return values in your editor, because the editor knows the types of the parameters and return values. It helps users quickly filter through the list of functions to find the one they need. For instance, if they are looking for a function that combines two strings, they can filter out functions that don't take two strings as parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here, we'll use TypeScript to add type information. TypeScript, &lt;a href="https://medium.com/@easycodingschool13/typescript-is-fastest-growing-programming-language-4c1ec9462ef8#:~:text=According%20to%20the%20JetBrains%20Status,growing%20programming%20language%20in%202022." rel="noopener noreferrer"&gt;one of the fastest growing programming languages&lt;/a&gt;, is a strongly typed language built on JavaScript that enhances code quality and maintainability, while improving developer productivity.&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="cm"&gt;/**
 * Adds two values and returns the sum.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your editor, you'll see the type information for the parameters and return value when you hover over the function:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-1.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-1.webp" alt="Getting type information for parameters and return value in VSCode on hover" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a user types &lt;code&gt;sum(&lt;/code&gt; in their editor, they'll see the type information for the parameters:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-2.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-2.webp" alt="Getting type information in the parameters on VSCode" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the return value, you can immediately get completion for methods on the returned &lt;code&gt;number&lt;/code&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-3.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftype-information-example-3.webp" alt="Getting completion options for number type in VSCode" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tags, tags, tags
&lt;/h2&gt;

&lt;p&gt;JSDoc supports a variety of tags that can be used to provide additional information about your symbols, such as &lt;code&gt;@param&lt;/code&gt; for parameters, &lt;code&gt;@returns&lt;/code&gt; for the return value, or &lt;code&gt;@typeParam&lt;/code&gt; for type parameters. Here's an example of a function with type information and tags:&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="cm"&gt;/**
 * Find a substring in a string and return the index of the first occurrence.
 *
 * @param value The string that will be searched for the needle.
 * @param needle The substring to search for in the string.
 * @returns The index of the first occurrence of the needle in the value, or -1 if the needle is not found.
 */&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;needle&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your editor, you'll see the type information for the parameters and return value, as well as the additional information provided by the tags:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftags-example-1.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftags-example-1.webp" alt="Seeing param info in the hover on VSCode" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On JSR, the tags are rendered in HTML. Here's an example of the &lt;code&gt;@param&lt;/code&gt; and &lt;code&gt;@return&lt;/code&gt; tags in the JSDoc of the&lt;br&gt;
&lt;a href="https://jsr.io/@std/fs/doc/~/move" rel="noopener noreferrer"&gt;&lt;code&gt;move&lt;/code&gt; function in &lt;code&gt;deno_std/fs&lt;/code&gt;&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftags-example-2.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Ftags-example-2.webp" alt="Seeing param and return value information on JSR" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Add examples to JSDoc
&lt;/h2&gt;

&lt;p&gt;Examples are another great way to help users quickly understand how to use your library. This is especially useful for functions that have complex behavior or many parameters. Examples can be added to your JSDoc comments using the &lt;code&gt;@example&lt;/code&gt; tag:&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="cm"&gt;/**
 * Find a substring in a string and return the index of the first occurrence.
 *
 * @example Find a substring in a string
 * ```

ts
 * const value = "hello world";
 * const needle = "world";
 * const index = find(value, needle); // 6
 *

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

&lt;/div&gt;

&lt;p&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;@example Find a substring in a string that doesn't exist&lt;/li&gt;
&lt;li&gt;```

ts&lt;/li&gt;
&lt;li&gt;const value = "hello world";&lt;/li&gt;
&lt;li&gt;const needle = "foo";&lt;/li&gt;
&lt;li&gt;const index = find(value, needle); // -1
*

&lt;code&gt;
*/
declare function find(value: string, needle: string): number;
&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best examples are concise and demonstrate the most common use cases of your function. They should be easy to understand and can be copied and pasted into a&lt;br&gt;
project.&lt;/p&gt;

&lt;p&gt;You can even provide multiple examples if there are multiple use cases worth mentioning. Here's an example of how multiple examples appear on JSR from &lt;a href="https://jsr.io/@std/fs/doc/~/move" rel="noopener noreferrer"&gt;the &lt;code&gt;move&lt;/code&gt; function of &lt;code&gt;deno_std/fs&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * (truncated for brevity)
 * @example Basic usage
 * ```

ts
 * import { move } from "@std/fs/move";
 *
 * await move("./foo", "./bar");
 *

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

&lt;/div&gt;

&lt;p&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This will move the file or directory at &lt;code&gt;./foo&lt;/code&gt; to &lt;code&gt;./bar&lt;/code&gt; without&lt;/li&gt;
&lt;li&gt;overwriting.
*&lt;/li&gt;
&lt;li&gt;@example Overwriting&lt;/li&gt;
&lt;li&gt;```

ts&lt;/li&gt;
&lt;li&gt;import { move } from "@std/fs/move";
*&lt;/li&gt;
&lt;li&gt;await move("./foo", "./bar", { overwrite: true });
*

&lt;code&gt;
*
* This will move the file or directory at `./foo` to `./bar`, overwriting
* `./bar` if it already exists.
*/
&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the text immediately following &lt;code&gt;@example&lt;/code&gt; serves as the title, and the text beneath the example becomes its description on JSR:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fexamples-on-jsr.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Fexamples-on-jsr.webp" alt="How examples appear on JSR" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But what should I document?
&lt;/h2&gt;

&lt;p&gt;You should document every symbol your package exports, including functions, classes, interfaces, and type aliases.&lt;/p&gt;

&lt;p&gt;This extends beyond just one JSDoc comment per symbol. For classes and interfaces for example, you should document the symbol itself, each method or property on it, including constructors. Here's an example of &lt;a href="https://jsr.io/@oak/oak/16.0.0/application.ts#L44" rel="noopener noreferrer"&gt;an Oak interface with JSDoc comments on its properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Base interface for application listening options. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ListenOptionsBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** The port to listen on. If not specified, defaults to `0`, which allows the
   * operating system to determine the value. */&lt;/span&gt;
  &lt;span class="nl"&gt;port&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="cm"&gt;/** A literal IP address or host name that can be resolved to an IP address.
   * If not specified, defaults to `0.0.0.0`.
   *
   * __Note about `0.0.0.0`__ While listening `0.0.0.0` works on all platforms,
   * the browsers on Windows don't work with the address `0.0.0.0`.
   * You should show the message like `server running on localhost:8080` instead of
   * `server running on 0.0.0.0:8080` if your program supports Windows. */&lt;/span&gt;
  &lt;span class="nl"&gt;hostname&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="nl"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** An optional abort signal which can be used to close the listener. */&lt;/span&gt;
  &lt;span class="nl"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&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;This is&lt;br&gt;
&lt;a href="https://jsr.io/@oak/oak/doc/~/ListenOptionsBase" rel="noopener noreferrer"&gt;how the JSDoc comments on properties appear on JSR&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Finterface-example-1.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Finterface-example-1.webp" alt="How property documentation appears on JSR" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your package consists of &lt;em&gt;multiple modules&lt;/em&gt;, adding a JSDoc comment to the top of each module file with the &lt;code&gt;@module&lt;/code&gt; tag will be helpful. This module comment should include a description and examples of how to use its exported symbols.&lt;/p&gt;

&lt;p&gt;Here's an example of &lt;code&gt;@module&lt;/code&gt; in &lt;a href="https://jsr.io/@oak/oak" rel="noopener noreferrer"&gt;Oak&lt;/a&gt;'s&lt;br&gt;
&lt;code&gt;application.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Contains the core concept of oak, the middleware application. Typical usage
 * is the creation of an application instance, registration of middleware, and
 * then starting to listen for requests.
 *
 * # Example
 *
 * ```

ts
 * import { Application } from "jsr:@oak/oak@14/application";
 *
 * const app = new Application();
 * app.use((ctx) =&amp;gt; {
 *   ctx.response.body = "hello world!";
 * });
 *
 * app.listen({ port: 8080 });
 *

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

&lt;/div&gt;

&lt;p&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;@module
*/
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In JSR, the first paragraph becomes the description beneath the modules on the &lt;a href="https://jsr.io/@oak/oak@16.0.0/doc" rel="noopener noreferrer"&gt;main docs page&lt;/a&gt; of your package:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-module-description.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-module-description.webp" alt="Oak module description" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the main doc page only includes the first paragraph. The subsequent JSDoc comment appears when you click through to &lt;a href="https://jsr.io/@oak/oak@16.0.0/doc/application/~" rel="noopener noreferrer"&gt;the module page&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-application-class-description.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-application-class-description.webp" alt="Oak's application module description" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use markdown for a better documentation experience
&lt;/h2&gt;

&lt;p&gt;Using markdown in JSDoc lets you organize your documentation in a more readable and engaging way. This can help you create documentation that is easier to understand, and allows you to link to external resources or other parts of your documentation using links.&lt;/p&gt;

&lt;p&gt;Some &lt;a href="https://www.markdownguide.org/basic-syntax/" rel="noopener noreferrer"&gt;useful markdown features&lt;/a&gt; you can use in your JSDoc comments include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;# my heading&lt;/code&gt; for section headings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;- hello world&lt;/code&gt; for bullet points&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;**important**&lt;/code&gt; for emphasis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_noteworthy_&lt;/code&gt; for italics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt; quote&lt;/code&gt; for block quotes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[foo](https://example.com)&lt;/code&gt; for links&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;`console.log("foo")`&lt;/code&gt; for inline code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On JSR, you can also use &lt;code&gt;[!IMPORTANT]&lt;/code&gt; to highlight important information in your documentation that you want to draw attention to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Copyright 2018-2024 the oak authors. All rights reserved. MIT license.&lt;/span&gt;

&lt;span class="cm"&gt;/** Middleware that converts the oak specific context to a Fetch API standard
 * {@linkcode Request} and {@linkcode Response} along with a modified context
 * providing some of the oak functionality. This is intended to make it easier
 * to adapt code to work with oak.
 *
 * There are two functions which will "wrap" a handler that operates off a
 * Fetch API request and response and return an oak middleware. The
 * {@linkcode serve} is designed for using with the {@linkcode Application}
 * `.use()` method, while {@linkcode route} is designed for using with the
 * {@linkcode Router}.
 *
 * &amp;gt; [!IMPORTANT]
 * &amp;gt; This is not intended for advanced use cases that are supported by oak,
 * &amp;gt; like integrated cookie management, web sockets and server sent events.
 * &amp;gt;
 * &amp;gt; Also, these are designed to be very deterministic request/response handlers
 * &amp;gt; versus a more nuanced middleware stack which allows advanced control.
 * &amp;gt; Therefore there is no `next()`.
 * &amp;gt;
 * &amp;gt; For these advanced use cases, create middleware without the wrapper.
 *
 * @module
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module-level JSDoc comment will appear at the top level in JSR as such:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-markdown-example.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Foak-markdown-example.webp" alt="Markdown in JSDoc example" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Link internally to other parts of your documentation
&lt;/h2&gt;

&lt;p&gt;Sometimes, your documentation refers to another symbol within your package. To make it easy for your users to navigate throughout your docs, you can link&lt;br&gt;
&lt;em&gt;within&lt;/em&gt; your documentation using the &lt;a href="https://jsdoc.app/tags-inline-link" rel="noopener noreferrer"&gt;&lt;code&gt;@link&lt;/code&gt; , &lt;code&gt;@linkcode&lt;/code&gt; , and &lt;code&gt;@linkplain&lt;/code&gt;&lt;/a&gt; tags. These tags accept a name paths or URL, from which it generates an HTML&lt;br&gt;
anchor element. Here's an example:&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="cm"&gt;/** Options to use when styling text with the {@linkcode print} function. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StyleOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** The color to print the message in. */&lt;/span&gt;
  &lt;span class="nl"&gt;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;black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Whether to print the message in bold. */&lt;/span&gt;
  &lt;span class="nl"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Whether to print the message in italic. */&lt;/span&gt;
  &lt;span class="nl"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A function that prints a message to the terminal with the given options.
 *
 * Note that on some versions of Windows, {@linkcode StyleOptions.color} may not
 * be supported in combination with {@linkcode StyleOptions.bold}.
 */&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StyleOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In VSCode, the hover tooltip now includes clickable links that will take you directly to the code where that symbol is defined:&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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Flinkcode-example-1.webp" 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%2Fdeno.com%2Fblog%2Fdocument-javascript-package%2Flinkcode-example-1.webp" alt="Example of linkcode in VSCode hover tooltip" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's an example of how &lt;code&gt;@linkcode&lt;/code&gt; appears in JSR. In the JSDoc for Oak's &lt;code&gt;serve&lt;/code&gt; function, it references &lt;code&gt;Application&lt;/code&gt;, which is becomes a clickable link on JSR:&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%2F3v4n8a54hgagn9tro5b4.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%2F3v4n8a54hgagn9tro5b4.gif" alt="Example of linkcode in JSR" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also reference built-in JavaScript objects, like &lt;code&gt;ArrayBuffer&lt;/code&gt;, and JSR will automatically link to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" rel="noopener noreferrer"&gt;the relevant MDN documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep JSDoc up-to-date with code changes
&lt;/h2&gt;

&lt;p&gt;One benefit about using JSDoc is that writing documentation in the comments happens at the same time as writing the code. That means anytime we need to make a change to a function, interface, or module, we can make the necessary change to the JSDoc with minimal context switching costs.&lt;/p&gt;

&lt;p&gt;But how does one make sure that the docs in the comments are updated? Starting with docs, á la docs-driven-development, can help you spec out and reason about the requirements before writing a single line of code. Sometimes, this means catching potential problems earlier and saving time from having to re-write code. (For a more macro-level approach to this, check out &lt;a href="https://www.youtube.com/watch?v=23xzRCoDZf4" rel="noopener noreferrer"&gt;"Readme-driven development"&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;If you've included code examples in your documentation, you can type check them from the command line with &lt;a href="https://docs.deno.com/runtime/manual/basics/testing/documentation" rel="noopener noreferrer"&gt;&lt;code&gt;deno test --doc&lt;/code&gt;&lt;/a&gt;. This is a helpful tool to ensure that your examples within your documentation are up-to-date and working.&lt;/p&gt;

&lt;p&gt;For example, building on our &lt;code&gt;sum&lt;/code&gt; function from earlier, let's add an example with a code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Adds two values and returns the sum.
 *
 * @example
 * ```

ts
 * import { sum } from "jsr:@deno/sum";
 * const finalValue = sum(1, "this is a string"); // 3
 *

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

&lt;/div&gt;

&lt;p&gt;*/&lt;br&gt;
export function sum(value1: number, value2: number): number {&lt;br&gt;
  return value1 + value2;&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Then, we can check the code in this example block by running `deno test --doc` :



```jsx
deno test --doc
Check file:///Users/sum.ts$8-13.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
const finalValue = sum(1, "this is a string");
                          ~~~~~~~
    at file:///Users/main.ts$8-13.ts:2:27
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Whoops! After we fix the type error in the code in our documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;deno&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;
&lt;span class="nx"&gt;Check&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;///Users/sum.ts$8-13.ts&lt;/span&gt;

&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nf"&gt;failed &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This offers a quick way to type check your code examples in your documentation before publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit your JSDoc
&lt;/h2&gt;

&lt;p&gt;If you're publishing to JSR, it will handle all of the formatting and generation of documentation based off your JSDoc-style comments. However, if you're interested in using your tooling to audit or test what the JSDoc comment output looks like, here are some suggestions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.deno.com/runtime/manual/tools/documentation_generator" rel="noopener noreferrer"&gt;&lt;code&gt;deno doc &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/a&gt;: This Deno command will print the JSDoc documentation for each of the&lt;code&gt;file&lt;/code&gt;'s exported members. This command also accepts an&lt;code&gt;--html&lt;/code&gt;flag that generates a static site with documentation, and a &lt;code&gt;--json&lt;/code&gt; flag to generate JSON output that you can display yourself.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.deno.com/runtime/manual/tools/documentation_generator#linting" rel="noopener noreferrer"&gt;&lt;code&gt;deno doc --lint&lt;/code&gt;&lt;/a&gt;`: This command will check for problems, such as missing return type or missing JSDoc comment on a public type. These lints help you write better documentation and catch potential issues before you publish.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.deno.com/runtime/manual/basics/testing/documentation" rel="noopener noreferrer"&gt;&lt;code&gt;deno test --doc&lt;/code&gt;&lt;/a&gt;`: We mentioned this command earlier in this post, but this allows you to easily type check your documentation examples.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jsdoc &amp;lt;directory&amp;gt;&lt;/code&gt;: &lt;a href="https://jsdoc.app/about-commandline" rel="noopener noreferrer"&gt;JSDoc's own CLI&lt;/a&gt; can generate a static documentation site with its default template, and offers a variety of configuration option flags. If the default template is a bit boring, there are others such as &lt;a href="https://github.com/clenemt/docdash" rel="noopener noreferrer"&gt;docdash&lt;/a&gt;, which provides hierarchical navigation and syntax highlighting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;Writing good JSDocs for your JavaScript package is critical to its success.&lt;br&gt;
Let's recap the best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write a concise summary&lt;/strong&gt;: The first paragraph of your JSDoc comment should be a concise description of the symbol that helps users quickly understand what it does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide good type information&lt;/strong&gt;: Type information helps users quickly filter through the list of functions and find the one they need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use tags&lt;/strong&gt;: Tags like &lt;code&gt;@param&lt;/code&gt;, &lt;code&gt;@returns&lt;/code&gt;, and &lt;code&gt;@typeParam&lt;/code&gt; provide more information about specific parts of your function or class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add examples&lt;/strong&gt;: Examples help users quickly understand how to use your library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document everything&lt;/strong&gt;: Document every symbol you are exposing in your package, including whole modules if you expose multiple modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link internally&lt;/strong&gt;: Use &lt;code&gt;@link&lt;/code&gt;, &lt;code&gt;@linkcode&lt;/code&gt;, and &lt;code&gt;@linkplain&lt;/code&gt; to link to other parts of your documentation to help users navigate your docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your documentation&lt;/strong&gt;: Use &lt;code&gt;deno test --doc&lt;/code&gt; to type check your documentation examples before publishing, and &lt;code&gt;deno doc --lint&lt;/code&gt; to check for
issues in your JSDoc comments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these best practices, you can create comprehensive documentation for your package that helps users get up and running with your package as quickly as possible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨️ Read more about JSR 🚨️&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jsr.io" rel="noopener noreferrer"&gt;JSR.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/jsr_open_beta" rel="noopener noreferrer"&gt;JSR announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsr.io/docs/introduction" rel="noopener noreferrer"&gt;Intro to JSR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/how-we-built-jsr" rel="noopener noreferrer"&gt;How we built JSR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/jsr-is-not-another-package-manager" rel="noopener noreferrer"&gt;JSR is not another package manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>jsr</category>
      <category>typescript</category>
    </item>
    <item>
      <title>An intro to TSConfig for JavaScript Developers</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Tue, 23 Apr 2024 17:16:39 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/an-intro-to-tsconfig-for-javascript-developers-49g8</link>
      <guid>https://dev.to/thisisjofrank/an-intro-to-tsconfig-for-javascript-developers-49g8</guid>
      <description>&lt;p&gt;JavaScript is constantly evolving, from its roots as a simple scripting language into a robust, modern tool for building complex applications. To manage larger, complicated code bases, JavaScript developers are constantly looking for ways to improve their workflows, their code quality and productivity. TypeScript is a major innovation towards improving code quality and maintenance by adding types, so it’s no surprise that&lt;br&gt;
&lt;a href="https://medium.com/@easycodingschool13/typescript-is-fastest-growing-programming-language-4c1ec9462ef8#:~:text=According%20to%20the%20JetBrains%20Status,growing%20programming%20language%20in%202022." rel="noopener noreferrer"&gt;it’s one of the fastest growing languages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TypeScript can feel scary if you've never used a compiled language or a compiler before. Or maybe you have, and you encountered a complex &lt;code&gt;tsconfig.json&lt;/code&gt; file that you didn’t completely understand. This blog post is an introduction to TypeScript (TS) and how to configure your project to use TypeScript with ease:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From JS to TS&lt;/li&gt;
&lt;li&gt;
TSConfig settings

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compilerOptions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Other TSConfig settings&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Additional features and capabilities of TSConfig&lt;/li&gt;

&lt;li&gt;What’s next&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨️ &lt;strong&gt;Want to write and run TypeScript without any config?&lt;/strong&gt; 🚨️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deno.land/manual/advanced/typescript" rel="noopener noreferrer"&gt;Deno natively supports TypeScript.&lt;/a&gt;&lt;br&gt;
Simply create a &lt;code&gt;.ts&lt;/code&gt; file and run &lt;code&gt;deno run yourfile.ts&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  From JS to TS
&lt;/h2&gt;

&lt;p&gt;TypeScript is built on top of JavaScript. It's a superset — any valid JavaScript is valid TypeScript. If you're new to TypeScript, it's easy to think of it as a "super powered linter", adding new features to the language to help you write JavaScript safely. It is designed to be strictly additive — TypeScript with the types stripped out is just JavaScript, but with types present, you get a much improved tooling, debugging and general developer experience.&lt;/p&gt;

&lt;p&gt;Because the JavaScript ecosystem has grown organically over time, TypeScript aims to fit in with your existing tooling. Modern editors, build tools, package managers, testing frameworks and CI/CD tools all integrate with TypeScript. In order to adopt TypeScript, and tailor it to your specific project requirements and tools, you will need to configure the TypeScript compiler. This can be done using a file called &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're doing TypeScript for the first time in a new codebase, you will probably leave most of the options in &lt;code&gt;tsconfig.json&lt;/code&gt; as default. For projects with tooling that needs interop, or has particular quirks, &lt;code&gt;tsconfig.json&lt;/code&gt; offers all the leavers you may need to pull to interact with your ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  TSConfig settings
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;tsconfig.json&lt;/code&gt; file allows you to configure how the TypeScript compiler processes your TypeScript code. The &lt;code&gt;tsconfig.json&lt;/code&gt; file is just a JSON object with properties that define your compiler options and project settings. We’ll go through some of the properties you’re likely to need when setting up your own &lt;code&gt;tsconfig.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The first property to take a look at is &lt;code&gt;compilerOptions&lt;/code&gt;, where you specify compiler settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compiler settings in &lt;code&gt;compilerOptions&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;compilerOptions&lt;/code&gt; property is where you define TypeScript compiler settings.&lt;br&gt;
These options include -&lt;/p&gt;

&lt;p&gt;&lt;code&gt;target&lt;/code&gt; - Specifies the ECMAScript target version for the emitted JavaScript. Defaults to ES3. To ensure maximum compatibility, set this to the lowest version that your code requires to run. &lt;code&gt;ESNext&lt;/code&gt; setting allows you to target the &lt;a href="https://github.com/tc39/proposals" rel="noopener noreferrer"&gt;latest supported proposed features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;module&lt;/code&gt; - Defines the module system to use (&lt;code&gt;CommonJS&lt;/code&gt;, &lt;code&gt;AMD&lt;/code&gt;, &lt;code&gt;ES6&lt;/code&gt;, etc.). Usage depends on your project’s requirements and the environment that your code will run in. Most modern projects will use &lt;code&gt;ES6&lt;/code&gt; or &lt;code&gt;ESNext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;outDir&lt;/code&gt; - Specifies the output directory for compiled JavaScript files. Usually set to &lt;code&gt;dist&lt;/code&gt; to create a dist directory for your compiled files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;strict&lt;/code&gt; - Enables strict type-checking options to help catch errors in your code. Set to &lt;code&gt;true&lt;/code&gt; for strict type checking.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;alwaysStrict&lt;/code&gt; - Automatically set to &lt;code&gt;true&lt;/code&gt; if &lt;code&gt;strict&lt;/code&gt; is enabled, this parses the code in JavaScript strict mode and emits &lt;code&gt;use strict&lt;/code&gt; for each source file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;esModuleInterop&lt;/code&gt; - In JavaScript, there are two main module systems: ECMAScript modules (ESM) and CommonJS modules (CJS). They have different syntax and semantics for imports and exports. When working in a TypeScript project that uses both ESM and CJS modules, setting &lt;code&gt;esModuleInterop&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; ensures that TypeScript handles imports and exports in a way that is compatible with both module systems. This is recommended if you are working with third party libraries that use both CJS and ESM.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lib&lt;/code&gt; - Specifies the libraries to include when type-checking. TypeScript includes type declarations (type definitions) for JavaScript built-in objects, such as Array, String, Promise, etc. These declarations define the shape and behavior of these objects, allowing TypeScript to provide accurate type checking and IntelliSense support. By default, TypeScript includes a standard set of library declarations (&lt;code&gt;dom&lt;/code&gt;, &lt;code&gt;es5&lt;/code&gt;, &lt;code&gt;es6&lt;/code&gt;, etc.) based on the ECMAScript version targeted by your project. However, you can customize the included libraries using the "lib" option to match your project's environment more precisely. For example, if you're targeting only Node.js, you might exclude browser-specific declarations like &lt;code&gt;dom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sourceMap&lt;/code&gt; - Generates source map files (.map) to aid debugging. Source maps are files that map the generated JavaScript code back to its original TypeScript source code. When using debugging tools, source maps allow you to set breakpoints, inspect variables, and step through your TypeScript code as if you were debugging the original TypeScript source. Set to &lt;code&gt;true&lt;/code&gt; to enable the use of source maps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other settings that may be useful:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jsx&lt;/code&gt; - If you are using JSX (for example with React), this setting determines &lt;a href="https://github.com/microsoft/TypeScript-Handbook/blob/master/pages/JSX.md" rel="noopener noreferrer"&gt;how your JSX files should be treated&lt;/a&gt;(&lt;code&gt;preserve&lt;/code&gt;, &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-native&lt;/code&gt;, etc.).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;removeComments&lt;/code&gt; - Strips comments from your compiled code. Helpful if minifying your compiled code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sourceRoot&lt;/code&gt; - Specifies the location where your debugger should locate TypeScript files instead of source locations. Use this flag if the sources located at run-time are in a different location than that at design-time. The location specified will be embedded in the source map to direct your debugger.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Other TSConfig settings&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;include&lt;/code&gt; - specifies an array of file paths or glob patterns that TypeScript should include in the compilation process. Only files matching the specified patterns will be considered for compilation. You can use glob patterns (e.g. "src/*&lt;em&gt;/&lt;/em&gt;.ts") to include files from specific directories or with specific file extensions. If include is not specified, TypeScript includes all &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, and &lt;code&gt;.d.ts&lt;/code&gt; files in the project directory by default.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;exclude&lt;/code&gt; - This setting specifies an array of file paths or glob patterns that TypeScript should exclude from the compilation process (even if they match the patterns specified in the &lt;strong&gt;&lt;code&gt;include&lt;/code&gt;&lt;/strong&gt; setting). You can use &lt;strong&gt;&lt;code&gt;exclude&lt;/code&gt;&lt;/strong&gt; to ignore files or directories that you don't want to be compiled, such as test files, build artifacts, or third-party libraries. Usually you will want to exclude your &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Additional features and capabilities of TSConfig&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Declaration Maps&lt;/strong&gt; - TypeScript can generate declaration map files (&lt;code&gt;.d.ts.map&lt;/code&gt;) alongside declaration files (&lt;code&gt;.d.ts&lt;/code&gt;) if &lt;code&gt;declarationMap&lt;/code&gt; is set to true in your &lt;code&gt;tsconfig.json&lt;/code&gt;. Declaration maps serve a similar purpose to source maps but are specific to TypeScript declaration files. These declaration maps provide mappings between the generated declaration files and their corresponding source map files, aiding in debugging and providing better tooling support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch Mode&lt;/strong&gt; - TypeScript's watch mode &lt;code&gt;tsc --watch&lt;/code&gt; monitors changes to your TypeScript files and automatically recompiles them whenever they're modified. This is useful during development, as it speeds up the feedback loop and eliminates the need to manually trigger compilation after each change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental Builds&lt;/strong&gt; - TypeScript's incremental builds feature keeps track of changes to your project's files and dependencies, allowing it to only rebuild the parts of your project that have changed since the last compilation. This can improve compilation times for large projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Override Options&lt;/strong&gt; - You can override specific compiler options for individual files or sets of files using comment directives in your TypeScript source files. For example, you can disable certain strict checks with &lt;code&gt;// @ts-ignore&lt;/code&gt; or specify custom compiler options with&lt;code&gt;// @ts-nocheck&lt;/code&gt; for specific sections of code.&lt;/p&gt;

&lt;p&gt;Use your &lt;code&gt;tsconfig.json&lt;/code&gt; tile as a gateway to unlock the full potential of TypeScript in your projects. By understanding its purpose and leveraging its capabilities, you can embrace TypeScript with confidence, and gain a more reliable, efficient, and enjoyable development experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To get you started there is a &lt;a href="https://github.com/tsconfig/bases" rel="noopener noreferrer"&gt;community owned repository of TSConfig base files&lt;/a&gt; for your chosen runtime&lt;/strong&gt;, then all you need to focus on is the unique choices for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;More and more developers are using TypeScript to build higher quality code bases and be more productive. Hopefully, this post demystifies setting up a new TypeScript project using &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if you’re interested in diving into TypeScript without having to do any configuration,&lt;br&gt;
&lt;a href="https://deno.land/manual/advanced/typescript" rel="noopener noreferrer"&gt;Deno supports TypeScript natively&lt;/a&gt;. Simply create a &lt;code&gt;.ts&lt;/code&gt; file, write some types, and run it immediately with&lt;br&gt;
&lt;code&gt;deno run your_file.ts&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>deno</category>
    </item>
    <item>
      <title>My experience of Modern Frontends Conference</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Fri, 18 Nov 2022 13:59:46 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/my-experience-of-modern-frontends-conference-1cgg</link>
      <guid>https://dev.to/thisisjofrank/my-experience-of-modern-frontends-conference-1cgg</guid>
      <description>&lt;p&gt;I was reached out to by Modern Frontends conference back in June this year. November is the most busy month of my DevRel calendar, I always expect to be travelling and speaking a lot around this time. As far as I was concerned, this was another interested conference who wanted me to speak. I submitted via Sessionize, after being reached out to by the conference on twitter, with a TBC talk title and description and was accepted to speak. This by itself isn’t completely unusual, but it was a little odd as I didn’t at that point know who was behind the conference. They had reached out to me on twitter via a ‘modern frontends’ twitter handle and didn’t sign off their messages from anyone, doing some sleuthing on the website didn’t bring any clarification. I assumed that it must be someone who knows me and had accepted my blank CFP on faith. &lt;/p&gt;

&lt;p&gt;I later discovered that the organiser of Modern Frontends is Gen Ashley, she mentions that she has a team working with her, but has not named them, nor given them credit for their work.&lt;/p&gt;

&lt;p&gt;Modern frontends then reached out to the company I work at for sponsorship, we eventually declined to sponsor due to clashes.&lt;/p&gt;

&lt;p&gt;Deciding to speak at Modern Frontends was easy for me, I live in the area of the conference and I already have prepared talks. My job is in developer advocacy, so my time writing and giving the talk is covered by my salary.&lt;/p&gt;

&lt;p&gt;However this is not the case for everyone, I have heard that speakers who had to travel not only didn’t have their travel covered, but no hotel either. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making speakers, especially new speakers, pay to attend and speak at your conference is unacceptable.&lt;/strong&gt; Paying for travel and paying for a hotel counts as paying to attend. The speakers &lt;strong&gt;are the content&lt;/strong&gt;, without them there is no conference. Compensate speakers for their time. It feels absolutely bizarre to me that this even needs to be said. ‘Networking opportunities at the event’ or ‘exposure’ do not count as compensation, as they say, people die of exposure. The least a conference should do is cover travel and accommodation, offering a speaker stipend is what conferences &lt;strong&gt;should&lt;/strong&gt; do.&lt;/p&gt;

&lt;p&gt;Because I work in DevRel, I am often contractually obliged not to take a speaker fee, usually, I will ask that it be donated to diversity tickets and scholarships, but I do appreciate when a conference compensates me for my time and effort in other ways. I have spoken at many conferences and usually they will do something nice for their speakers, like a gift in the hotel room, or a tour of the local area, or a speaker dinner or combination of the above. NDC gives a donation to a charity in the speakers’ name, for example.&lt;/p&gt;

&lt;p&gt;I have seen multiple people online making statements to the above effect and being virtually silenced, shut down, shamed and bullied by Gen Ashley into retracting and deleting their posts. She has been rude to speakers, sponsors and anyone who has called her out. Speakers who have stood down from speaking at the event still have their faces plastered all over the conference website and associated advertising.&lt;/p&gt;

&lt;p&gt;When I arrived at the event itself it was pretty grim, the venue was hard to find, unbranded, starkly lit. It was giving corporate-strip-light-carpet-tiled-hell vibes. The sponsors had tables around the hallway and were clearly unimpressed with the setup and the event itself. Sponsors were sold on 3000 attendees, there were certainly not 3000 people at the event, perhaps a few hundred.&lt;/p&gt;

&lt;p&gt;I have spoken openly about my discomfort around the excess and entitlement that is shown at developer events, with free food, clothing and other swag being given out to the already fortunate and well situated. However this event took things to the other extreme. For the price of a £600 ticket, attendees were treated to a single coffee service, with urns of pre-made coffee and tea and a lunch of ‘packet sandwiches’. It was swept away within an hour too, so late attendees or those working over their lunch-break did not get a lunch. I don’t want to sound entitled, but for the price of the sponsorship and the quantity of sponsors, along with the price of the tickets, and the fact that they weren’t paying their speakers (I assume they might have paid some of the bigger names) it feels like the funds have been spent elsewhere…&lt;/p&gt;

&lt;p&gt;Where the funds &lt;strong&gt;weren’t&lt;/strong&gt; spent was on recording and livestreaming the talks. There was no recording equipment in any of the rooms. When I asked Gen about what had happened to the livestream she just told me that she was going to refund the people who had paid for livestream tickets and offer them a free ticket to next year’s event (ha!). At the time I asked, tickets for the livestream were still available for purchase on the event website at £43. This feels very close to fraud to me. I know people have been asking online for refunds, I hope they get them soon. I hope that if the event runs next year it is better managed.&lt;/p&gt;

&lt;p&gt;I gave my talk and it went well, there was no one in the room to set me up, just an AV setup leaning against the podium. I set myself, up, gave the talk and left the room.  As I walked through the hallway Gen came up to me and told me that she thought it was ‘funny’ that people were complaining about the event on twitter, and that she didn’t care. I really didn’t know what to respond to this, so I said that it wasn’t really funny and seemed quite serious and left it at that.&lt;/p&gt;

&lt;p&gt;My final point to make about this conference is that it was booked to run on the same day that HalfStack London is on every year. Dylan has been running an absolutely excellent, inclusive and creative front end focused conference for almost 10 years on the same day every year. Modern Frontends booked to run on exactly the same day, which shows either rudeness or a lack of market research. Dylan ended up moving the conference to run on the Wednesday before Modern Frontends ran and it was a roaring success.&lt;/p&gt;

&lt;p&gt;I know others have shared their experiences, and I do not want to center myself in this debacle, but it also does not sit well with me to watch this unfold and say nothing. I always advocate for fairness and diversity in tech and I will keep doing so. I apologise if my involvement in this conference and silence up until now has hurt anyone.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Visualize your commits in realtime with Ably and GitHub webhooks</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Fri, 12 Aug 2022 11:32:00 +0000</pubDate>
      <link>https://dev.to/ably/visualize-your-commits-in-realtime-with-ably-and-github-webhooks-2pf2</link>
      <guid>https://dev.to/ably/visualize-your-commits-in-realtime-with-ably-and-github-webhooks-2pf2</guid>
      <description>&lt;p&gt;Have you ever wanted to see the work of your entire engineering organization in a visualization as it happens? In this article, I'll tell you how I used Github webhooks and Netlify serverless functions, along with a simple Svelte web app, to do just this &lt;a href="https://github-vis.ably.dev/" rel="noopener noreferrer"&gt;in my new interactive visualizer tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F500qq0ppkqrctwygpi01.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%2F500qq0ppkqrctwygpi01.gif" alt="A gif of the visualiser in action. New nodes are appearing on a force directed graph diagram" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graphical visualizations are a fun way to see progress and to help your organizations to understand that, while tech work can sometimes seem invisible, the amount of effort involved in building software is a huge team endeavour!&lt;/p&gt;

&lt;p&gt;In the early 2000s, a tool was built that could visualize your software version history, called &lt;a href="https://gource.io/" rel="noopener noreferrer"&gt;Gource&lt;/a&gt;. It was built for CVS server, and later ported to work with Subversion, and later, finally git. Gource displays changes to a repository in an animated force tree diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=NjUuAuBcoqs" rel="noopener noreferrer"&gt;A video of Gource as a visualization tool&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While Gource is an excellent and powerful tool, it only works locally, you have to install the software and run it. It parses historic commit log data, and generates a video file with the visualization using OpenGL. It doesn’t show data in realtime.&lt;/p&gt;

&lt;p&gt;Taking inspiration from Gource, and with the advantage of the versatility of the web in 2022, I decided to build a modern visualization tool that works in a browser, in realtime. I wanted to depict code committed to a GitHub repository, or organization, as and when it is pushed or merged.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;What you need to build a GitHub Visualizer:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To send a message via WebSockets every time a commit gets pushed.&lt;/li&gt;
&lt;li&gt;A hosting platform to provide a payload URL&lt;/li&gt;
&lt;li&gt;To convert commit data into a file/folder hierarchy.&lt;/li&gt;
&lt;li&gt;A tool to display the commit data in a graphical form.&lt;/li&gt;
&lt;li&gt;Some code to stop the app from crashing if it runs for a long time or receives millions of messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;GitHub webhooks to the rescue&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;GitHub offers &lt;a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks" rel="noopener noreferrer"&gt;webhooks &lt;/a&gt;which allow integrations to subscribe to events on github.com. Triggering an event by, for example, pushing a commit, will send an HTTP POST payload to a URL configured in your GitHub account. Webhooks can be installed for an entire organization, or per repository, which means that I can get commit data in realtime over HTTP. The next task is setting up a hosting provider to accept the webhook data and provide a callback.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;How to host an app on Netlify&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My GitHub Visualizer is hosted on &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;. Netlify, and other cloud products, provide native support for &lt;a href="https://docs.netlify.com/functions/overview/" rel="noopener noreferrer"&gt;serverless functions&lt;/a&gt; which let us run server-side code without having to manage a server ourselves. These serverless functions can be bundled with a web application, and are version controlled, built and deployed along with the rest of the app. By binding a serverless function to the GitHub Visualizer web application I could set up that function as the target of a GitHub webhook. Once the data arrives from GitHub I could publish it to the UI using Ably (&lt;a href="https://github.com/ably-labs/github-commit-visualizer/blob/4b25f57f042c1bc4c2d4ef1519e7aacbf84d06ff/api/github-hook/index.ts#L10" rel="noopener noreferrer"&gt;see the code&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  How to build the UI with Svelte
&lt;/h2&gt;

&lt;p&gt;Building a single page web application that is reactive to incoming changes in data usually requires a framework. I chose to build the GitHub Visualizer with &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; because of its &lt;a href="https://svelte.dev/blog/svelte-3-rethinking-reactivity" rel="noopener noreferrer"&gt;simple approach to state management&lt;/a&gt;, readable syntax, small size and speed. The app consists of two Svelte components; &lt;a href="https://github.com/ably-labs/github-commit-visualizer/blob/4b25f57f042c1bc4c2d4ef1519e7aacbf84d06ff/app/src/lifecycle-functions/LifecycleFunctions.ts#L1" rel="noopener noreferrer"&gt;one&lt;/a&gt; that subscribes to an &lt;a href="https://ably.com/docs/realtime/channels" rel="noopener noreferrer"&gt;Ably channel&lt;/a&gt; and keeps a GitHub commit history stored as a variable in the application, and &lt;a href="https://github.com/ably-labs/github-commit-visualizer/blob/4b25f57f042c1bc4c2d4ef1519e7aacbf84d06ff/app/src/SourceGraph.svelte#L20" rel="noopener noreferrer"&gt;another&lt;/a&gt; that can render the GitHub commit history as a "&lt;a href="https://en.wikipedia.org/wiki/Force-directed_graph_drawing" rel="noopener noreferrer"&gt;force directed graph&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;When a message arrives from Ably, it is pushed to the history variable. Svelte has built in support for reactive variable assignment, which means that when a new value is assigned to that variable, the change will be immediately reflected in the UI. The next piece of the puzzle is visualizing the data.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to make a force directed graph with VisJS
&lt;/h2&gt;

&lt;p&gt;In order to display the GitHub data in a graph, I used &lt;a href="https://visjs.org/" rel="noopener noreferrer"&gt;VisJS&lt;/a&gt;, a library specially designed to handle large amounts of dynamic data. The graph is rendered in the browser in an HTML canvas, making it possible to show complex animations efficiently. The &lt;a href="https://visjs.github.io/vis-network/docs/network/" rel="noopener noreferrer"&gt;VisJS network component&lt;/a&gt; was exactly what I needed to display a complex diagram of “nodes” and their connecting lines, called “edges”. Nodes and edges are represented in a JS objects, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var nodes = new vis.DataSet([
    {id: 1, label: 'Node 1'},
    {id: 2, label: 'Node 2'},
    {id: 3, label: 'Node 3'},
    {id: 4, label: 'Node 4'}
]);

var edges = new vis.DataSet([
    {from: 1, to: 3},
    {from: 1, to: 2},
    {from: 2, to: 4},
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means that the data that the app receives from the GitHub webhook needs to be converted into this format to be rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to convert GitHub webhooks data into JS objects
&lt;/h2&gt;

&lt;p&gt;When a message arrives, the app will iterate over the commits in the message. It will store the file names that have been created, modified and deleted. If a file has been created then the app will split up each part of the file path and create a "node", and an "edge" connecting each to its parent part, eventually meeting at the root in the centre, which is the repository. Every node created has the full path to this point as an id, and each edge gets its id set to "{from}=&amp;gt;{to}" so that they can be found by file path in the future.&lt;/p&gt;

&lt;p&gt;An example node might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    id: "docs/content/tutorials/github-vis.js",
    label: "github-vis.js"
    group: "docs",
    isFile: true,
    isRepository: false,
    isDirectory: false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an example edge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    from: "docs/content/tutorials"
    to: "docs/content/tutorials/github-vis.js"
    id: "docs/content/tutorials/=&amp;gt;docs/content/tutorials/github-vis.js"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every created file new nodes are made and for any removed file nodes are deleted. Edges are generated for all of the created and modified nodes. The app also keeps a queue (a first in first out array) of every node that is created, and when it reaches a maximum number of nodes to display, it removes the oldest items from the top of the array. This will stop the app from consuming unbounded amounts of memory if an organization is very busy and millions of messages arrive. Nodes that are modified are removed from the queue and put back on the end of it, so that they aren't prematurely removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to display the data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The data is now in a structure which represents the files present in the last N commits (however many you decide to store). The next job is to write some Svelte code that runs each time the data structure changes, which will do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the currently displayed nodes&lt;/li&gt;
&lt;li&gt;Find any nodes that are in the data structure that haven't yet been rendered&lt;/li&gt;
&lt;li&gt;Add those nodes to the force directed graph&lt;/li&gt;
&lt;li&gt;Add new edges to the graph to join the added nodes&lt;/li&gt;
&lt;li&gt;Remove any nodes that aren't in the data structure any more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VisJS has a reactive DataSet that can be used for this, the app just needs to to check the node IDs and add/remove nodes for it to trigger a re-render. &lt;a href="https://github.com/ably-labs/github-commit-visualizer/blob/4b25f57f042c1bc4c2d4ef1519e7aacbf84d06ff/app/src/SourceGraph.svelte#L33" rel="noopener noreferrer"&gt;Diffing the data set and data structure&lt;/a&gt; like this means that we don't continually recreate all the nodes on each commit. It also means that the physics applied to the graph by VisJS (the force which makes the nodes repel one another) is consistent and they won’t bounce around.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to verify GitHub webhook data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There is one final gotcha in this project, it is necessary to ensure that folks can't just push data at our API and break the app. Luckily, GitHub supports signing webhooks with a &lt;a href="https://en.wikipedia.org/wiki/HMAC" rel="noopener noreferrer"&gt;HMAC signature&lt;/a&gt;. You make up a password and put it in the secret field in GitHub when you set up your webhooks. In the serverless function, we use the crypto module built into NodeJS to verify the HMAC and only forward it over Ably channels if the signatures match the shared secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Using this in your organization&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository: &lt;a href="https://github.com/ably-labs/github-commit-visualizer.git" rel="noopener noreferrer"&gt;https://github.com/ably-labs/github-commit-visualizer.git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://app.netlify.com/start/deploy?repository=https://github.com/ably-labs/github-commit-visualizer" rel="noopener noreferrer"&gt;Deploy it to Netlify&lt;/a&gt; (or other hosting/serverless functions provider), remembering to set your Ably API key and GitHub webhooks secret as environment variables.&lt;/li&gt;
&lt;li&gt;Add the function URL as a GitHub hook from your org or repository to your deployed app.&lt;/li&gt;
&lt;/ol&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%2Fj3ww6594wrsla189nnt3.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%2Fj3ww6594wrsla189nnt3.gif" alt="Gif of the flow of adding a webhook to your github account" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Watch as your beautiful visualizations pop into existence!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Potential extensions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are so many ways that this project could be expanded. There is a lot of data about the commit returned in the payload from GitHub, so the visual could be extended to show usernames next to file changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Other ideas&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Colour code the nodes by latest changes, make the older updates start to fade as newer ones come in&lt;/li&gt;
&lt;li&gt;Make nodes link to their respective file in GitHub on click&lt;/li&gt;
&lt;li&gt;Add a rewind option, so that you can look back in time&lt;/li&gt;
&lt;li&gt;Add some UI to pick a maximum number of nodes shown, for those on smaller screens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code for this project is &lt;a href="https://github.com/ably-labs/github-commit-visualizer" rel="noopener noreferrer"&gt;open source and available on GitHub&lt;/a&gt;, in our Ably-Labs organisation. Please take it and make it your own. I am very open to PRs and do let me know if you find any &lt;a href="https://github.com/ably-labs/github-commit-visualizer/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt;. I hope you enjoy visualizing your own projects!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>github</category>
      <category>websockets</category>
    </item>
    <item>
      <title>Build your own live chat web component with Ably and AWS</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Fri, 16 Jul 2021 13:45:03 +0000</pubDate>
      <link>https://dev.to/thisisjofrank/build-your-own-live-chat-web-component-with-ably-and-aws-f1d</link>
      <guid>https://dev.to/thisisjofrank/build-your-own-live-chat-web-component-with-ably-and-aws-f1d</guid>
      <description>&lt;p&gt;Web Components are a great way to build reusable functionality you can use in different web pages and web apps. Imagine sharing components between different frameworks like React, Vue.js, or Next.js! In this post, we delve into Web Components and show you how to build a chat web component with Ably to use it in an application built with AWS Amplify and AWS Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Web Component?
&lt;/h2&gt;

&lt;p&gt;Web Components are a collection of web technologies, standardized in the browser, to ease writing reusable pieces of markup and functionality. They’re a combination of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener noreferrer"&gt;Custom Elements&lt;/a&gt;, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" rel="noopener noreferrer"&gt;Shadow DOM&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement" rel="noopener noreferrer"&gt;HTML templates&lt;/a&gt;. When they’re used all together, they’re collectively called &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" rel="noopener noreferrer"&gt;Web Components&lt;/a&gt;.&lt;br&gt;
 Web Components are an alternative to framework-specific approaches to component reuse such as React Components, and Vue.js templates. Web Components are interesting because they’re not bound to any runtime framework, and they’re supported by all modern browsers. Competing approaches to components for the web were established prior to Web Components being widely supported, which caused a lot of the frameworks to develop their own approaches to reuse and encapsulation. Web components offer a framework-independent way of building reusable functionality for the browser.&lt;/p&gt;
&lt;h2&gt;
  
  
  The basics of a Web Component
&lt;/h2&gt;

&lt;p&gt;When talking about Web Components, most people refer to Custom HTML Elements, as these are the closest thing to the component models found in React or Vue. Custom Elements can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render HTML into themselves by setting their own innerHTML properties.&lt;/li&gt;
&lt;li&gt;Encapsulate data by storing it as attributes on instances of themselves.&lt;/li&gt;
&lt;li&gt;Track changes to data stored in their attributes, and trigger a re-render.&lt;/li&gt;
&lt;li&gt;Allow you to write code that is triggered when they are added and removed from the DOM.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Writing a basic web component
&lt;/h2&gt;

&lt;p&gt;The first thing we'll do in this post is create an example of a Custom Element that displays the number of times a button has been clicked. To create this Custom Element, we need to create a new JavaScript file that we can reference from our web page. Start by creating a new file called &lt;em&gt;index.js&lt;/em&gt;. We'll define a class for the Custom Element and call it &lt;code&gt;CountComponent&lt;/code&gt;. This will extend HTMLElement (the browser's base type for all its elements):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Inside this class, we need to define the attributes to be observed for &lt;code&gt;change tracking&lt;/code&gt;. For the sake of this demo, we will return an array of a single string – the attribute name &lt;code&gt;count&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Next, we need to define some custom attributes and their behaviour. We create a &lt;code&gt;getter&lt;/code&gt;for the custom “count” attribute, and in the function, the attribute value &lt;code&gt;count&lt;/code&gt; is loaded with a call to &lt;code&gt;getAttribute&lt;/code&gt;, defaulting to the &lt;em&gt;string&lt;/em&gt; &lt;code&gt;"0"&lt;/code&gt;. Once the data is loaded, we’re parsing it from JSON – we do this because any data that we store must be a string.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We also need to create a &lt;em&gt;setter&lt;/em&gt; for our attribute, and here we’re serializing the value to JSON and setting it on our element:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Even though Custom Elements are defined as classes, you can’t put any logic inside their constructors other than calling &lt;code&gt;super&lt;/code&gt; to trigger the base logic for the HTML Element class. &lt;em&gt;Add any other logic, or miss out calling super and you’ll see an error in your console and the element won’t work&lt;/em&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Because we can’t do any meaningful work in the constructor, Custom Elements provide &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks" rel="noopener noreferrer"&gt;lifecycle callbacks&lt;/a&gt; – functions that you can implement at different parts of the component lifecycle. We will use the &lt;code&gt;connectedCallback&lt;/code&gt; function. Implementing this callback will cause the code to run when the component is added to the DOM – it is similar to React's &lt;code&gt;componentDidMount&lt;/code&gt; function. In connectedCallback, we’re setting a default for the counter, and calling a function we’ll look at shortly called &lt;code&gt;setContent&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The next lifecycle callback function available to us is &lt;code&gt;disconnectedCallback&lt;/code&gt; (shown here only for illustration purposes as we’re not running any code). The code in this function executes when your element is removed from the DOM and destroyed.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Custom Elements also provide us with an &lt;code&gt;attributeChangedCallback&lt;/code&gt;. We will use this function to execute code whenever an &lt;code&gt;observedAttribute&lt;/code&gt; changes:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;setContent&lt;/code&gt; is a function that the component calls when the component is added to the DOM, and when an update happens. The click handler in the above code can access the attributes of the element using the &lt;code&gt;this&lt;/code&gt; keyword, so on each click, we increment the value, in turn triggering a re-render.&lt;br&gt;
&lt;em&gt;Note: This demo is not particularly performance focused, in that it sets the entire innerHTML and wires up a click handler to the button each time it’s called. This is not how one would build an application for production! In a real application, you would not reset your entire DOM. Instead you would only change the parts of the UI that required updating. Furthermore, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" rel="noopener noreferrer"&gt;Shadow DOM APIs&lt;/a&gt; could be used in more performance-sensitive scenarios.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, at the end of the JavaScript file, we call the browser's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define" rel="noopener noreferrer"&gt;customElements.define API&lt;/a&gt; – providing a tag name for the element, and a reference to the class we just defined.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Using the basic web component
&lt;/h2&gt;

&lt;p&gt;It is now possible to reference the Custom Element JavaScript file as a module and add the element we just made to the page using the tag name that we defined:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And the component renders thus&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%2F2ad7nnz7qgrb8pl9khg9.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%2F2ad7nnz7qgrb8pl9khg9.png" alt="screenshot of rendered component" width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Chat web component with Ably
&lt;/h2&gt;

&lt;p&gt;We're going to build a text chat component – Ably Chat component – by building &lt;em&gt;two&lt;/em&gt; Custom Elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AblyBaseComponent.js – a base component that encapsulates Ably interactions&lt;/li&gt;
&lt;li&gt;AblyChatComponent.js – an element that inherits from AblyBaseComponent and implements chat logic on top of Ably.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The base component is designed strictly to be built upon, and to encapsulate the basic requirements for Ably API key management and subscribing to channels. Using this base component, we could build any kind of Custom Element that relies on realtime messaging.&lt;/p&gt;

&lt;p&gt;In order to connect to Ably, you need an &lt;a href="https://ably.com/signup" rel="noopener noreferrer"&gt;Ably account&lt;/a&gt; and an&lt;a href="https://knowledge.ably.com/setting-up-and-managing-api-keys" rel="noopener noreferrer"&gt; API key&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Base Component
&lt;/h3&gt;

&lt;p&gt;The base component starts with the standard Custom Element boilerplate – AblyBaseComponent extends HTMLElement, and calls its constructor.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the connectedCallback function, we call &lt;code&gt;this.connectToAblyChannel&lt;/code&gt; (which we’ll examine shortly):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The disconnectedCallback function loops through &lt;code&gt;this.subscribedChannels&lt;/code&gt; (which we will create in a &lt;code&gt;connect&lt;/code&gt; function) and unsubscribes from any Ably channels that have been used:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;connectToAblyChannel&lt;/code&gt; function is where a lot of the real work happens. We start off by loading some configuration – we will set up a convention that we expect users to supply &lt;em&gt;either&lt;/em&gt; their Ably API key, or a callback URL to an API that responds with an Ably token authentication request. To supply these values, and because we’re creating HTML elements, &lt;strong&gt;we expect them to be set as attributes on the element when it’s created in the markup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In order to read the data attribute when the element is connected, we expect the element to look as follows:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Next we need to design a way to configure the Ably client. In regular JavaScript you might use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document" rel="noopener noreferrer"&gt;Document API&lt;/a&gt; to interact with elements in the DOM – but because we’re literally &lt;em&gt;inside &lt;/em&gt;the custom element, we can use DOM API calls like &lt;a href="https://www.w3schools.com/jsref/met_element_getattribute.asp" rel="noopener noreferrer"&gt;getAttribute&lt;/a&gt; from the &lt;code&gt;this&lt;/code&gt; object.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This code tries to load &lt;code&gt;data-api-key&lt;/code&gt; and &lt;code&gt;data-get-token-url&lt;/code&gt;. If an API key is found, it takes precedence, otherwise, we create an Ably JavaScript SDK configuration object that contains an “authUrl” property. Now that we have a configuration object (either an API key or a URL), we can create an instance of the Ably Realtime JavaScript SDK. &lt;em&gt;It is important to point out that this Custom Element relies on the containing page having already included the Ably JavaScript SDK using a script element before it is executed.&lt;/em&gt; The following code comes after the configuration, inside the &lt;code&gt;connectToAblyChannel&lt;/code&gt; function:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Once we have the SDK instance, stored in the variable &lt;code&gt;this.ably&lt;/code&gt;, we will also create an empty array called &lt;code&gt;subscribedChannels&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’re going to conclude with two additional functions in the AblyBaseComponent – a &lt;code&gt;publish&lt;/code&gt; function and a &lt;code&gt;subscribe&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The regular Ably SDK exposes publish and subscribe functions once you have called &lt;code&gt;ably.channels.get(channelName)&lt;/code&gt;to get a channel. For this chat example, we want to let the AblyChatComponent decide which channels it will publish and subscribe on, effectively extending the API surface area of the Ably SDK (the set of things the API can do).&lt;/p&gt;

&lt;p&gt;The augmented publish and subscribe functions take an extra parameter at the start – the channel name – then pass on the rest of the arguments to the Ably SDK to do all the hard work for us. We do this by destructuring the &lt;em&gt;arguments&lt;/em&gt; array, and ignoring the first element. This allows us to capture all of the parameters, except the first one, in our newly defined variable &lt;code&gt;args&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We can then use &lt;code&gt;.apply&lt;/code&gt; on the publish or subscribe calls from the Ably SDK to pass the rest of the variables to the SDK to publish or subscribe to messages.&lt;/p&gt;

&lt;p&gt;We will use the first parameter, the &lt;code&gt;channelName&lt;/code&gt;, to get the correct channel and keep track of it so we can unsubscribe when we unmount from the DOM. The following code will go inside the &lt;code&gt;publish&lt;/code&gt; function, below the arguments assignment. The subscribe function works similarly&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;As you can see, both &lt;code&gt;publish&lt;/code&gt; and &lt;code&gt;subscribe&lt;/code&gt; are virtually identical functions – with the exception that we’re passing to them the call to &lt;code&gt;channel.publish&lt;/code&gt; or &lt;code&gt;channel.subscribe&lt;/code&gt;, respectively.&lt;/p&gt;

&lt;p&gt;This all might seem a little framework-like, but what it means is that developers building Ably components on top of this base class can call &lt;code&gt;publish&lt;/code&gt; or &lt;code&gt;subscribe&lt;/code&gt; with an extra &lt;code&gt;channelName&lt;/code&gt; parameter at the start of the call, and not worry about connecting or disconnecting from Ably channels.&lt;/p&gt;

&lt;p&gt;This base class contains all our Ably code, and is now ready for us to build exciting components on top of it, so let’s create our second component, the &lt;strong&gt;AblyChatComponent&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Chat Component
&lt;/h3&gt;

&lt;p&gt;These components are designed to be imported as an ES6 Module – where the script tag you use in your HTML has &lt;code&gt;type="module"&lt;/code&gt;as an attribute. When using ES6 modules, we can use &lt;code&gt;import&lt;/code&gt; in browser components, so to start here, we’re importing our AblyBaseComponent in order to extend it.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The next thing to do is set up some boilerplate code for the element. Define an attribute called “messages”, and set it up to be observable. Next, set up &lt;code&gt;constructor&lt;/code&gt;, which in turn calls the constructor of the &lt;strong&gt;AblyBaseComponent&lt;/strong&gt; using a call to &lt;code&gt;super();&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We can use &lt;code&gt;connectedCallback&lt;/code&gt; to configure the chat application. We call &lt;code&gt;super.connectedCallback&lt;/code&gt; to trigger all of the Ably configuration logic from the base component. The following code goes below &lt;code&gt;constructor&lt;/code&gt;, inside the closing bracket of the &lt;code&gt;AblyChat&lt;/code&gt; class:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We call the &lt;code&gt;super.subscribe* *&lt;/code&gt;function that was defined on the base to subscribe to messages on a channel called &lt;code&gt;chat&lt;/code&gt;. The rest of the parameters, as we pointed out earlier, are standard Ably JavaScript SDK parameters – we’re subscribing only to messages on the topic &lt;code&gt;chat-message&lt;/code&gt;. When a message is received, we call a function called &lt;code&gt;this.onAblyMessageReceived&lt;/code&gt; (which we’ll implement in a moment), passing the received message as an argument.&lt;/p&gt;

&lt;p&gt;In order to ensure that any CSS styles applied to the page don't affect the component, and vice versa, inside the body of &lt;code&gt;connectedCallback&lt;/code&gt;, we will generate a random string and assign it to a property called &lt;code&gt;id&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Next we call a function named &lt;code&gt;renderTemplateAndRegisterClickHandlers&lt;/code&gt;, which we’ll look at shortly.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Finally, we place the browser focus on an element called &lt;code&gt;this.inputBox&lt;/code&gt; that is generated when the template is rendered, so that people using the chat UI will be able to instantly start typing.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Next we use the &lt;code&gt;attributeChangedCallback&lt;/code&gt; to update the innerHTML of the chat bubbles in the chat window when messages are received. When an attribute is changed, the &lt;code&gt;innerHTML&lt;/code&gt; of &lt;code&gt;this.chatText&lt;/code&gt; is set and scrolled into view. We’re using a function called &lt;code&gt;formatMessages&lt;/code&gt;, which takes the message history, and converts it to HTML elements fit for display:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Next we set up the &lt;code&gt;renderTemplateAndRegisterClickHandlers&lt;/code&gt; function, which is named for what it does! The start of this function calls another function called &lt;code&gt;defaultMarkup&lt;/code&gt;, that takes a single parameter – the *id *of the element, and returns the innerHTML we want to display on the screen – an empty chat box element.&lt;/p&gt;

&lt;p&gt;Once the element has been rendered into the DOM, we can use &lt;code&gt;querySelectorAll&lt;/code&gt; to find the chatText, inputBox, sendButton, and messageEnd elements so that we can use them in our code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Inside we also wire up the eventListeners for clicks on the sendButton and on each keyPress in the input box so that we can process user input.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We mentioned the &lt;code&gt;onAblyMessageReceived&lt;/code&gt; function earlier when we subscribed to Ably messages – this function takes the current message history from &lt;code&gt;this.messages&lt;/code&gt;, makes sure it is at most 199 messages long, and adds the latest message to the end of the array:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This new array is then assigned to &lt;code&gt;this.messages&lt;/code&gt;, which triggers the UI to re-render because &lt;code&gt;this.messages&lt;/code&gt; is an observed property.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sendChatMessage&lt;/code&gt; function is called either when Enter is pressed, or when the button to send a message is clicked. Because we’re extending the AblyBaseComponent, it calls the &lt;code&gt;super.publish&lt;/code&gt; function, passing the channel name and the Ably message payload:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You can see that it’s also responsible for clearing the text box and focusing on it so the user can continue chatting.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;handleKeyPress&lt;/code&gt; function is triggered on each keypress. If the keypress is the Enter key &lt;em&gt;and&lt;/em&gt; there is a message in the chat box, it sends the chat message:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;formatMessages&lt;/code&gt; function is responsible for mapping the Ably message history into span elements. There’s a little bit of logic here to detect whether or not the message was sent by the current user of the app by checking the &lt;code&gt;message.connectionId&lt;/code&gt; property against the &lt;code&gt;ably.connection.id&lt;/code&gt; property, and adding a &lt;code&gt;me&lt;/code&gt; or &lt;code&gt;other&lt;/code&gt; CSS class that can apply styles to. The &lt;code&gt;data&lt;/code&gt; property from the message is used to carry the received text message.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The above concludes the custom element class. After the custom element class ends, we have two functions. The first is the &lt;code&gt;uuidv4()&lt;/code&gt; function – which generates a unique &lt;code&gt;id&lt;/code&gt; for the component:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The second is &lt;code&gt;defaultMarkup&lt;/code&gt; which takes a single parameter – the ID of the component – and uses it to set the &lt;code&gt;id&lt;/code&gt; property on the generated HTML.&lt;/p&gt;

&lt;p&gt;Once we’ve set this &lt;code&gt;id&lt;/code&gt; property, we can embed CSS that is scoped specifically to this element ID directly into the output code. This means that if multiple instances of the custom element appear on the same page, they won’t have conflicting &lt;em&gt;ids&lt;/em&gt; or &lt;em&gt;styles&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At the bottom of this next snippet you can see the markup for the component – a &lt;code&gt;div&lt;/code&gt; for holding message history, and a &lt;code&gt;form&lt;/code&gt; for capturing user input, complete with the &lt;em&gt;class names&lt;/em&gt; used in our &lt;em&gt;query selector calls&lt;/em&gt; earlier. &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And of course, much like our example element at the start, we’re calling &lt;code&gt;customElements.define&lt;/code&gt; to register the HTML tag: &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The custom element is now functionally complete, and so long as both the AblyBaseComponent.js and AblyChatComponent.js files are included in a web application, they can be used by referencing our AblyChatComponent.js as a module.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Using the Chat Component
&lt;/h2&gt;

&lt;p&gt;To use the now-registered custom element, we use it like any old HTML tag and provide it with the correct attributes:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The element renders like this in the page, and when configured with an API key or get-token-url, it just works!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FuwCOKwbTPcG4wGVQf4tOkaoo7vUafvpDRdQLPLoQZyMfVZjYJHdCsqvx1pchii65WEKX-XWz6s_fNamj29iHCkBxpebLRyHV5uU7btXlVZCNq-CD31H5i4l9oqYJ1grUb12bZwtk" 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%2Flh3.googleusercontent.com%2FuwCOKwbTPcG4wGVQf4tOkaoo7vUafvpDRdQLPLoQZyMfVZjYJHdCsqvx1pchii65WEKX-XWz6s_fNamj29iHCkBxpebLRyHV5uU7btXlVZCNq-CD31H5i4l9oqYJ1grUb12bZwtk" alt="screenshot of finished web component" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  API Key Management
&lt;/h2&gt;

&lt;p&gt;As hinted above, we need to talk about API key management. While this custom element supports reading Ably API keys straight from your markup – which is great for local development and debugging – you &lt;em&gt;absolutely should not&lt;/em&gt; store your Ably API keys in your markup, otherwise they could get stolen and misused.&lt;/p&gt;

&lt;p&gt;The recommended way to use API keys in the front-end is to use &lt;a href="https://ably.com/documentation/core-features/authentication/#token-authentication" rel="noopener noreferrer"&gt;Ably Token Authentication&lt;/a&gt;. Token authentication is an exchange mechanism where you use your real API key to generate limited-use tokens that can be passed back to your clients to use in the front end.&lt;/p&gt;

&lt;p&gt;In order to use token authentication in this way, we need to create an API somewhere, to call from the front-end, that has your real Ably API key stored in it. We can then use a function in the Ably SDK to exchange your real API key for a token that gets returned to the Ably JavaScript SDK. The &lt;a href="https://github.com/ably/ably-js" rel="noopener noreferrer"&gt;Ably JavaScript SDK&lt;/a&gt; manages this token exchange process for you. When you provide a URL that points to an API that will return a token, the SDK will manage and refresh the token as required, so you don’t need to worry about it. This demo will walk through using AWS Lambda functions and the AWS API Gateway to achieve this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Serverless Token exchange with AWS Lambda
&lt;/h2&gt;

&lt;p&gt;The following example AWS Lambda function provides the necessary token exchange functionality. All we need to do is require the Ably JavaScript SDK, and create an instance of the &lt;code&gt;Ably.Realtime&lt;/code&gt; client passing your Ably API key from &lt;em&gt;process.env.ABLY_API_KEY.&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We use &lt;code&gt;client.auth.createTokenRequest&lt;/code&gt; to generate a temporary token, and return it back to the client.&lt;/p&gt;

&lt;p&gt;The onus is on the owner of the API key to make sure that the users requesting a temporary token have access to chat – you can authenticate the requests any way you’d like, and it’s no different from authentication in any other lambda function. In the next section, we go through hosting this on AWS Lambda&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AWS Lambda for Authentication
&lt;/h2&gt;

&lt;p&gt;To deploy to AWS Lambda, we need to create a new directory called /api/createTokenRequest with two files in it – package.json and index.js Here’s the package.json file&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And this is the index.js file&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;These two files, along with their node_modules are required by the AWS Lambda runtime. We're going to use npm to restore the node modules and then compress the contents of the /createTokenRequest directory into a zip file. In the terminal execute the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;After that, zip up the contents of the createTokenRequest directory (this process is OS dependent). We will use the AWS UI to create a Lambda function and upload this zip file as the source code.&lt;/p&gt;

&lt;p&gt;We'll walk through this process now. You need to &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;log in to your AWS account&lt;/a&gt; first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Search for lambda in the Services search bar, and click the Lambda services box that shows up in the results: &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%2Fzjvdujah0c2fvm24wy8h.png" alt="lambda1-2" width="800" height="528"&gt;
&lt;/li&gt;
&lt;li&gt; Click the “Create function” button to create a new Lambda function &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%2Fayqr4vc8qr1jvhipgc76.png" alt="lambda2" width="800" height="528"&gt;
&lt;/li&gt;
&lt;li&gt; Select "Author from scratch" and give your function a name, then click "Create function": &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%2F470xf8rq2gm7zewo4k4m.png" alt="lambda3" width="800" height="762"&gt;
&lt;/li&gt;
&lt;li&gt; Once the function has been created, click “Add trigger” to make the Lambda function accessible over HTTP: &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%2F5c74gheun4a5l1wgoozn.png" width="800" height="671"&gt;
&lt;/li&gt;
&lt;li&gt; Select “API Gateway” from the “Trigger configuration” dropdown &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%2F0661h7uwv1uh5tuehdvm.png" alt="lambda5" width="800" height="390"&gt;
&lt;/li&gt;
&lt;li&gt; On the resulting page, select your function from the dropdown and click "Add". &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%2F0ygyzys1k4imxw1xmlob.png" alt="lambda6" width="800" height="496"&gt;
&lt;/li&gt;
&lt;li&gt; Set the Deployment stage to default and the Security to Open, then click "Add". &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%2Fbj2uqbrgmfuzayzi7gmy.png" alt="lambda7" width="800" height="628"&gt;
&lt;/li&gt;
&lt;li&gt; Once you have added your trigger, the UI shows a URL in the Triggers tab under Configuration. (This is what you will add as the &lt;code&gt;data-get-token-url&lt;/code&gt; parameter when you use your component in the HTML, but we still have some more setup to do!) &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%2F8be7r88sg66qxm0amif2.png" alt="lambda13" width="800" height="655"&gt;
&lt;/li&gt;
&lt;li&gt; Now you need to upload the zip file that we created earlier. Click on the "Code" tab, then "Upload from" and select ".zip file": &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%2Fzbbucpdgn9m6frs78o5w.png" alt="lambda10-1" width="800" height="408"&gt;
&lt;/li&gt;
&lt;li&gt; Once the zip file is uploaded, you will need to set up your environment variables with your Ably API key. Under "Configuration", select "Environment variables", then click the "Edit" button: &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%2Fgtfpc7qphb1cr44nupnp.png" alt="lambda11" width="800" height="602"&gt;
Add your Ably API key to the “Environment variables” settings &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%2F892k1aprwo5p6lg5kgy9.png" alt="lambda12" width="800" height="479"&gt; And that's it, your Lambda is set up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it, your Lambda is set up&lt;/p&gt;

&lt;p&gt;In the index.js file we’re reading from &lt;code&gt;process.env.ABLY_API_KEY&lt;/code&gt;. You will need to &lt;a href="https://knowledge.ably.com/setting-up-and-managing-api-keys" rel="noopener noreferrer"&gt;generate a new ably API key&lt;/a&gt; and then define this environment variable, with it’s key value in the AWS UI (or using an automation tool of your preference).&lt;/p&gt;

&lt;p&gt;Once our Lambda function is created, we’ll need to add an AWS API Gateway Trigger to give our lambda an externally accessible URL. This is the URL that we can safely configure in our HTML markup in lieu of our actual API key. The Ably SDK will take care of the rest.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Hosting your Web Component on Amplify
&lt;/h2&gt;

&lt;p&gt;Now we can walk through hosting your component on Amplify, the &lt;a href="https://aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;AWS static web hosting service&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Search for Amplify in the Services search bar and click on the resulting AWS Amplify link.&lt;br&gt;&lt;br&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%2Fpqst955kxblwaqrb1v2h.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%2Fpqst955kxblwaqrb1v2h.png" alt="step1" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "GET STARTED"&lt;br&gt;&lt;br&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%2Fcwypt3h32hd6rd9cb4if.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%2Fcwypt3h32hd6rd9cb4if.png" alt="step2" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scroll down the resulting page to "Host your web app, and click on "Get started":&lt;br&gt;&lt;br&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%2Fcamebey6bmpqelra58zy.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%2Fcamebey6bmpqelra58zy.png" alt="step3" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authenticate AWS Amplify with your GitHub account&lt;br&gt;&lt;br&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%2F3ln4ip1kxx0cxd5vofqv.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%2F3ln4ip1kxx0cxd5vofqv.png" alt="step4" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the repository of your web component&lt;br&gt;&lt;br&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%2Fc3s7d8j3up2ht2cu6yuf.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%2Fc3s7d8j3up2ht2cu6yuf.png" alt="step5" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edit your build settings to include &lt;code&gt;npm run ci&lt;/code&gt; as a prebuild command and set the baseDirectory to "/build".&lt;br&gt;&lt;br&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%2Fmswstt1uer1kt4su6dj6.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%2Fmswstt1uer1kt4su6dj6.png" alt="step6" width="505" height="354"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "Save and deploy" button to host your component&lt;br&gt;&lt;br&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%2Fgtrgq5t85g1h3qv9yifi.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%2Fgtrgq5t85g1h3qv9yifi.png" alt="step7" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If all is well, the component will provision, build, and deploy successfully. The UI will provide you with a URL to view your hosted component.&lt;br&gt;&lt;br&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%2Fhjlhc42o3lmzryrpoqic.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%2Fhjlhc42o3lmzryrpoqic.png" alt="step8" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hosted web component will look something like this&lt;br&gt;&lt;br&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%2Fe4v4oudgmoypens9egd7.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%2Fe4v4oudgmoypens9egd7.png" alt="web-component" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Using the NPM package
&lt;/h2&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%2Fnu6aa4z3aq364ww5dhiy.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%2Fnu6aa4z3aq364ww5dhiy.png" alt="npm logo" width="800" height="112"&gt;&lt;/a&gt;&lt;br&gt;
Because components are built as plain old JavaScript, we can distribute and consume the component using NPM and any of the variety of browser-friendly ways to add NPM packages to your front end.&lt;/p&gt;

&lt;p&gt;Here at Ably, we’ve &lt;a href="https://www.npmjs.com/package/ably-chat-component" rel="noopener noreferrer"&gt;published the component to NPM&lt;/a&gt; as ably-chat-component, and you can reference it directly using the &lt;em&gt;Skypack&lt;/em&gt; CDN. This ensures packages are browser-compatible.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You need to reference the client-side Ably SDK for the component to work. However, once you have done that, you can reference the Skypack URL for our component, and add the ably-chat tag into your page, set your API key, and everything will just work.&lt;/p&gt;

&lt;p&gt;This is the simplest supported way to use this component in development mode. As mentioned above, however, you will need to &lt;em&gt;switch out your API key&lt;/em&gt; for a token request URL of your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Architecture
&lt;/h2&gt;

&lt;p&gt;Diagrammatically, the component architecture can be depicted as such:&lt;br&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%2Fy2h012eab89w97cs0cwm.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%2Fy2h012eab89w97cs0cwm.png" alt="architecture digagram" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the corresponding sequence diagram&lt;br&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%2F4gp1w1bw4n0ip6mvjstw.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%2F4gp1w1bw4n0ip6mvjstw.png" alt="sequence diagram" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this piece we’ve broken down how web components work, explored Ably and Web Components, and walked through how we can use AWS Amplify and AWS Lambda to host applications that support realtime chat.&lt;/p&gt;

&lt;p&gt;If you’ve already got a web application and know how to host it, we’ve also touched on how you can use Skypack to &lt;a href="https://www.npmjs.com/package/ably-chat-component" rel="noopener noreferrer"&gt;include this component directly from NPM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Chat is just one way that you can use realtime messaging and Web Components, and we’d love to see what you can do with this codebase.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a tamagotchi game with Realtime TFL Data — Tamago-Train!</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Thu, 30 Jul 2020 09:25:52 +0000</pubDate>
      <link>https://dev.to/ably/build-a-tamagotchi-game-with-realtime-tfl-data-tamago-train-j8d</link>
      <guid>https://dev.to/ably/build-a-tamagotchi-game-with-realtime-tfl-data-tamago-train-j8d</guid>
      <description>&lt;p&gt;The &lt;a href="https://trainagotchi.glitch.me/" rel="noopener noreferrer"&gt; Station Manager game&lt;/a&gt; with &lt;a href="https://www.ably.io/hub/products/10" rel="noopener noreferrer"&gt;Realtime Tube data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been learning a lot recently about using realtime data streams and how and why one might use them in an app. In order to better understand the differences between realtime streaming data and REST APIs (which I’ve had more experience with), I decided to build a game, the mechanic of which uses realtime train arrival data. As trains arrive into the station in real life, effects are triggered in the game which the user has to manage.&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%2Fy0597he5id1kolz339de.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%2Fy0597he5id1kolz339de.gif" width="800" height="820"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Make it your own!
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://glitch.com/~tamagotrain" rel="noopener noreferrer"&gt;All of the code for the game is on Glitch&lt;/a&gt;. This means that you can see the code, ‘remix’ it and make it your own. There is a thorough readme file in the repo and I’ll also go over some of the methods used in this blog post.&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting Arrivals Data
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.ably.io" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; has a &lt;a href="https://www.ably.io/hub/products/10" rel="noopener noreferrer"&gt;Hub of realtime data streams&lt;/a&gt; for developers to try out and build apps with. I used the &lt;a href="https://www.ably.io/hub/products/10" rel="noopener noreferrer"&gt;London Tube Schedule stream&lt;/a&gt;, which provides a stream of various data from TFL; including arrivals at a given station. For the tamagotchi game, I wanted to find out the arrivals at a busy station, so that I would have lots of trains arriving in close succession. I chose King’s Cross station for this reason. The data stream uses the station&lt;a href="https://data.gov.uk/dataset/ff93ffc1-6656-47d8-9155-85ea0b8f2251/national-public-transport-access-nodes-naptan" rel="noopener noreferrer"&gt; NAPTAN code&lt;/a&gt; rather than it’s name to get the correct data, so I had to look up the correct code for King’s Cross (you can &lt;a href="https://dsx-sources.herokuapp.com/source/tfl" rel="noopener noreferrer"&gt;look up station codes here&lt;/a&gt;), which is &lt;code&gt;940GZZLUKSX&lt;/code&gt;.&lt;br&gt;
The stream I’ll therefore be using is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[product:ably-tfl/tube]tube:940GZZLUKSX:arrivals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every time the data from TFL is updated, this channel will publish a message with the new arrival times of trains into Kings Cross. The joy of the data being a realtime stream means that I don’t have to poll for the data, as I would with a REST API, instead, I establish a connection once and data is published to the channel as and when updates happen.&lt;/p&gt;
&lt;h4&gt;
  
  
  Connecting to a Data Stream
&lt;/h4&gt;

&lt;p&gt;In order to connect to the data stream I used the&lt;a href="https://github.com/ably/ably-js" rel="noopener noreferrer"&gt; Ably Javascript SDK&lt;/a&gt;. To do so, I need an &lt;a href="https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys" rel="noopener noreferrer"&gt;Ably API key&lt;/a&gt; which comes with &lt;a href="https://www.ably.io/signup" rel="noopener noreferrer"&gt;a free account&lt;/a&gt;. To keep my API key safe, I also used &lt;a href="https://www.ably.io/documentation/core-features/authentication#token-authentication" rel="noopener noreferrer"&gt;Token Authentication&lt;/a&gt; which makes a &lt;a href="https://www.ably.io/documentation/realtime/authentication#token-request" rel="noopener noreferrer"&gt;Token Request&lt;/a&gt; on the server side which is handed to the &lt;a href="https://www.ably.io/documentation/realtime" rel="noopener noreferrer"&gt;Ably realtime client&lt;/a&gt; to authenticate. There is a brilliant walkthrough of how to implement Token Authentication here:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/ably-client-side-api-calls?path=index.html" alt="ably-client-side-api-calls on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h4&gt;
  
  
  TFL Data
&lt;/h4&gt;

&lt;p&gt;The data published by the stream looks a little like this ↓&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%2Fsw72euwo6lne04mcs5ly.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%2Fsw72euwo6lne04mcs5ly.png" alt="An example of the data published on the TFL stream" width="400" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a large array of train objects each with a lot of information. For the sake of this game, the only information I’m really interested in is the &lt;code&gt;TimeToStation&lt;/code&gt; value. I can use these numbers to calculate when to cause a train to arrive into the station in the game.&lt;/p&gt;

&lt;p&gt;I could have created all kinds of interesting extensions to the game, with multiple platforms and colour coded trains for their lines, even maybe an arrivals board with train destinations, but let’s not get too carried away…&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%2Ffkqsp6yvb85801cmoe28.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%2Ffkqsp6yvb85801cmoe28.gif" width="700" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Game mechanics
&lt;/h4&gt;

&lt;p&gt;Congratulations! You’re the newest TamagoTrain Station Controller! Now you’ve got to keep your station in fine fettle.&lt;br&gt;
Don’t let your station get too hot, don’t let your platform fill up with passengers, litter or mice!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Trains raise the temperature of your station, as do passengers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it gets too hot, passengers will start to faint!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unconscious passengers can’t leave the platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Passengers sometimes drop litter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Too much litter attracts mice!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trash and mice all take up space on the platform making it difficult for your passengers to exit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If your platform gets too full, too hot or too cold your station will have to shut and your game will end&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  How to play
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clean the platform to clear away litter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vent cold air through the station to keep everyone cool (but don’t get carried away!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Passengers departing through the exit will cool the platforms down a little&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Departing trains also cool the platform slightly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can charm mice with songs! They’ll find their way off the platform if musically enticed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Music will also wake up fainted passengers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Game Code
&lt;/h4&gt;

&lt;p&gt;The game is an &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;expressJS&lt;/a&gt; app. It is split into two parts — the simulation/game loop, which runs in ‘ticks’ and the ui/render loop which runs at 30 frames per second. This separation prevents us from tying the game logic to the frame rate, which will reduce the chance of the frame rate dropping if the game logic gets complicated. (If you’re interested, this is a &lt;a href="http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/" rel="noopener noreferrer"&gt;great intro to game loops&lt;/a&gt;.)&lt;/p&gt;
&lt;h4&gt;
  
  
  Game.js
&lt;/h4&gt;

&lt;p&gt;The Game.js file is the main control loop for the game - in it, we define a JavaScript class called &lt;code&gt;Game&lt;/code&gt;. When games are created, we create a new instance of this class to hold the game state. This is also where we set up the &lt;code&gt;tick()&lt;/code&gt; function, which is called once per second. This ‘tick’ steps the simulation forward by iterating the game loop. It ‘ticks’ the game entities (the platform, passengers and trains), applies any problems (adding litter and mice) and applies any buffs (cleaning, venting or music).&lt;/p&gt;

&lt;p&gt;The only input the user can supply is applying a &lt;code&gt;Buff&lt;/code&gt; — either &lt;code&gt;clean&lt;/code&gt;, &lt;code&gt;vent&lt;/code&gt; or &lt;code&gt;music&lt;/code&gt;, which are triggered by the buttons in the UI. When pressed, these buttons add a &lt;code&gt;Buff&lt;/code&gt; object to an array in the &lt;code&gt;Game&lt;/code&gt; instance, that we use as a queue of actions. Buffs can only be added to the queue a maximum of 3 times, after that clicking the buttons in the UI will just return until the &lt;code&gt;Buff&lt;/code&gt; has been taken off the queue.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Game&lt;/code&gt; instance is responsible for three core things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Handling train arrival/departure messages, and routing them to the platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating instances of &lt;code&gt;Buffs&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checking for &lt;strong&gt;game over&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the rest of the game logic happens in the &lt;code&gt;tick()&lt;/code&gt; functions found on the &lt;code&gt;Entities&lt;/code&gt;, &lt;code&gt;Problems&lt;/code&gt; and &lt;code&gt;Buffs&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  GameUI.js
&lt;/h4&gt;

&lt;p&gt;The GameUi.js file is where the rendering of the game happens. It uses an &lt;a href="https://pawelgrzybek.com/the-observer-pattern-in-javascript-explained/" rel="noopener noreferrer"&gt;observer pattern&lt;/a&gt; to keep track of the game state.&lt;/p&gt;

&lt;p&gt;30 times a second the &lt;code&gt;GameUI.draw()&lt;/code&gt; function is called and passed a snapshot of the game state. &lt;code&gt;GameUI&lt;/code&gt; instance keeps track of the last state it was called with so that it can avoid re-drawing things that have not changed.&lt;/p&gt;

&lt;p&gt;The GameUi class has a collection called &lt;code&gt;_renderingFunctions&lt;/code&gt; — a list of functions it calls in order, each being passed the current game state. If any rendering function returns a value of -1, we use this as a signal to stop drawing to the screen and to display the** Game Over** screen. The rendering code places absolutely positioned divs on the page which are styled in the css. The divs contain animated gifs of the entities, buffs and problems. The appearance of the divs is changed by adding css classes and data-attributes dependant on the problems or buffs that have been applied in the game state.&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%2F5790q13ybpfg08w25jr2.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%2F5790q13ybpfg08w25jr2.gif" width="150" height="150"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Entities, Buffs and Problems
&lt;/h4&gt;

&lt;p&gt;By default, when an instance of &lt;code&gt;Game&lt;/code&gt; is created, a &lt;code&gt;Platform&lt;/code&gt; entity is created. This platform has some basic state (an age measured in &lt;code&gt;ticks&lt;/code&gt;, a &lt;code&gt;width&lt;/code&gt;, a &lt;code&gt;height&lt;/code&gt;) along with the three core stats the game is ranked on - &lt;code&gt;hygiene&lt;/code&gt;, &lt;code&gt;temperature&lt;/code&gt; and &lt;code&gt;capacity&lt;/code&gt;. The game is won or lost based on the state of these variables, which the game evaluates each tick. As the game ticks, the &lt;code&gt;Game&lt;/code&gt; instance will process any objects in its queue &lt;a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)" rel="noopener noreferrer"&gt;first in first out&lt;/a&gt;, creating an instance of the requested &lt;code&gt;Buff&lt;/code&gt; and applying it to the &lt;code&gt;Platform&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;Platform&lt;/code&gt; ticks, the following things happen -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Any unprocessed messages are read, FIFO.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a message for a train arrival or departure is found a train is created on the platform or removed from it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All &lt;code&gt;tickables&lt;/code&gt; are &lt;code&gt;tick&lt;/code&gt;ed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any completed contents or buffs are removed — an item is deemed complete if a property &lt;code&gt;completed&lt;/code&gt; is present, and set to true on the object.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;tickables&lt;/code&gt; that the platform stores are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Any present train&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All of the contents of the platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All of the buffs applied to the platform&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On each tick, the item that is being &lt;code&gt;ticked&lt;/code&gt; gets handed the current instance of the platform, and based on the logic in that item’s class, it can mutate the properties of the platform. For instance - every tick, a &lt;code&gt;Mouse&lt;/code&gt; could reduce the &lt;code&gt;hygiene&lt;/code&gt; property of the platform.&lt;/p&gt;

&lt;p&gt;The rest of the entities, Buffs and Problems are all JavaScript classes that can mutate the state of the &lt;code&gt;Platform&lt;/code&gt; instance in their &lt;code&gt;tick&lt;/code&gt; method.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Both &lt;code&gt;Entities&lt;/code&gt; and &lt;code&gt;Problems&lt;/code&gt; have &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; coordinates that are used to move them around the user interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Problems&lt;/code&gt; all inherit from a &lt;code&gt;Base Class&lt;/code&gt; called &lt;code&gt;Problem&lt;/code&gt; which creates these properties by default for them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A problem looks like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Entities and problems hold state which will cause effects during the lifetime of a game. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Travellers walk towards the exit by moving 10 pixels closer to the exit each tick&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Travellers have a chance of dropping litter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Litter has a chance of adding mice to the platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trains add an extra Traveller to the platform every tick&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this logic exists in the &lt;code&gt;tick&lt;/code&gt; function of each kind of entity or problem.&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%2Fom6d8cfenmnmccb82p82.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%2Fom6d8cfenmnmccb82p82.gif" width="300" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Starting the Game
&lt;/h4&gt;

&lt;p&gt;The game uses &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;webpack&lt;/a&gt; to bundle the client side JavaScript. The script.js file is the webpack entry point, webpack bundles together all of the JavaScript files before they are sent to the browser. This is great because it means we only need to reference script.js to start the game.&lt;/p&gt;

&lt;p&gt;The script.js file is referenced in the index.html file and it takes care of starting new games. It contains a &lt;code&gt;startGame()&lt;/code&gt; function which does all the work:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Creates a &lt;code&gt;game&lt;/code&gt; instance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creates an instance of the &lt;code&gt;GameUI&lt;/code&gt; class, passing it a reference to the new &lt;code&gt;game&lt;/code&gt; instance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calls &lt;code&gt;game.start()&lt;/code&gt; passing a configuration object of two actions - one to execute on start, one on end.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the onGameStart action listens for events on the dataSource&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the onGameEnd action disconnects the dataSource to stop the game from using up messages that we don't need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;ui.startRendering()&lt;/code&gt; function is called which will set up the render loop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally the game is returned so that the UI buttons will work in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Game Over
&lt;/h4&gt;

&lt;p&gt;Game failure states are managed in the Game.js file, in the function &lt;code&gt;isGameOver()&lt;/code&gt;. This contains a collection of objects with functions for different failure conditions. At the start of each tick, each of those functions are run and if any of them return &lt;code&gt;true&lt;/code&gt; then the game is over.&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%2F7m9dxyo3l62nftumnwhm.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%2F7m9dxyo3l62nftumnwhm.gif" width="150" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Have fun!
&lt;/h4&gt;

&lt;p&gt;I hope you’ve enjoyed playing the game and will enjoy making your own versions, or even adding some extensions to mine. If you’ve any questions about the game or about realtime data streaming you can drop me a message in the comments or get me &lt;a href="https://twitter.com/ThisIsJoFrank" rel="noopener noreferrer"&gt;@thisisjofrank &lt;/a&gt;on twitter. I’d also love to see any remixes you make!&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%2Fiuztqcawla767if88mzs.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%2Fiuztqcawla767if88mzs.gif" width="450" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting started with IoT Wearables and MQTT</title>
      <dc:creator>Jo Franchetti</dc:creator>
      <pubDate>Wed, 27 May 2020 10:04:04 +0000</pubDate>
      <link>https://dev.to/ablydev/getting-started-with-iot-wearables-and-mqtt-2ed3</link>
      <guid>https://dev.to/ablydev/getting-started-with-iot-wearables-and-mqtt-2ed3</guid>
      <description>&lt;p&gt;&lt;em&gt;How to make your own internet connected t-shirt with LEDs that respond to realtime data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a developer advocate I get to make all kind of fun projects. I particularly like to make wearable technology, specifically, things with lots of lights on that can connect to the internet. Because I like to be sparkly! I started making wearable tech projects a couple of years ago, my first being a&lt;a href="https://medium.com/samsung-internet-dev/tweet-my-wedding-dress-e08fb90b097f"&gt; light up, tweet controlled wedding dress.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I started working with hardware, I had no real clue what I was doing and made a few very silly mistakes around choices of hardware and how to stick them all together, My hope for this blog is to give a little direction to those who are looking to get into hardware projects and to help you over that initial ‘hardware is scary’ hurdle.&lt;/p&gt;

&lt;p&gt;The project we’ll be making is a wearable LED array which responds to data sent from a web app. The app allows people to draw in a 16x16 pixel array, kind of like a low res version of MS paint, but multiplayer! In order to realise this dream we’ll need a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Firstly an array of lights which can change colour&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A microprocessor to control those lights&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An app where users can set colours&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A method of sending messages to and from the app and the microprocessor.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Making a wearable LED array
&lt;/h2&gt;

&lt;p&gt;Let’s start by building the array of lights. It is going to be a 16 by 16 grid of LEDs.&lt;/p&gt;

&lt;p&gt;The LEDs that we’ll be using in this project are called &lt;a href="http://www.thesmarthomehookup.com/beginners-guide-to-individually-addressable-rgb-led-strips/"&gt;Addressable RGB LEDs&lt;/a&gt;. Each LED has a tiny microcontroller which allows it to be lit up with a unique colour and brightness. Addressable means that we can target each LED individually, we send them an &lt;a href="https://www.w3schools.com/colors/colors_rgb.asp"&gt;RGB colour value&lt;/a&gt; (similar to that which you might be familiar with from CSS).&lt;/p&gt;

&lt;p&gt;For this project we’ll be using 5V LED strips. These are great as we don’t have to individually solder each LED, this also makes them nicely robust for a wearable, fewer solder points means fewer points of weakness where the connection could potentially snap - a lesson I sadly learnt the hard way with my dress project!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IDcFIFn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/0%2A5D9T0e2UteerJiJW" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IDcFIFn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/0%2A5D9T0e2UteerJiJW" alt="Addressable RGB LED strip"&gt;&lt;/a&gt;&lt;em&gt;Addressable RGB LED strip&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The strips can be bought by the meter or spool and you can cut them along the lines indicated to get them to the length you require. We’ll be cutting them into lengths of 16 LEDs. They also have clear labelling for which lines carry power, data and ground; which makes them easier to solder together correctly.&lt;/p&gt;

&lt;p&gt;Things to bear in mind when purchasing LED strip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The price of the strip increases as the pixel density increases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The voltage — we want 5V for this project so that we can run it with a USB battery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The colour of the ribbon itself — try and match the clothing you’ll put the array on&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The type of RGB LEDs (some are RGBW which will require you to send an extra “whiteness” value in your code)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I opted for a strip with a white ribbon (as the tshirt I’ll be mounting it in is white) and with LEDs that are 2cm apart. I cut them down to 16 strips with 16 lights per strip and laid them out in a square:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yxFxg5nW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A7h9g3XwazLA753gzu0bJpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yxFxg5nW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A7h9g3XwazLA753gzu0bJpw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To keep the strips safe in a wearable I sewed two piece of fabric together to make long pockets to insert each strip:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rupj5rIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AnT7xM2XxDH0tzazffo7yKg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rupj5rIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AnT7xM2XxDH0tzazffo7yKg.png" alt="Diagram of sewn pockets"&gt;&lt;/a&gt;&lt;em&gt;Diagram of sewn pockets&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(In actuality, my sewing was much more messy than this)&lt;/p&gt;

&lt;p&gt;Insert each strip into each pocket and carefully solder together each of the connections at ether end, to create continuous connections throughout the strips. Pay attention to the direction of the data line indicators on the strip while doing this. Connect together the power data and ground lines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_3Gyh9s5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AGTe_K5CM6WtthtNbKEsW_g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_3Gyh9s5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AGTe_K5CM6WtthtNbKEsW_g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can buy a soldering iron and solder from around £30 and I used single core copper wire to connect them all up (because it doesn’t fray so I find it easier to solder). There are lots of &lt;a href="https://www.instructables.com/id/How-to-solder/"&gt;tutorials&lt;/a&gt; and &lt;a href="https://youtu.be/Qps9woUGkvI"&gt;videos&lt;/a&gt; on soldering online so I won’t go into it here, but it isn’t too scary and once you’ve had a few goes at practising you’ll get the hang of it quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ktRptI4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2096/1%2Ax3mynl1GnVJfNaLLcADK7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ktRptI4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2096/1%2Ax3mynl1GnVJfNaLLcADK7g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once soldered, you will have a flexible LED array which can be sewn into clothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling the array
&lt;/h2&gt;

&lt;p&gt;So, now that we have a display, we need a way to actually control what colours are sent to it. A microprocessor will be useful here as they can do enough calculations to control the lights, but are nice and small so can be easily hidden in a wearable. For this project I am using the &lt;a href="https://learn.adafruit.com/adafruit-feather-huzzah-esp8266?view=all"&gt;Adafruit Feather Huzzah&lt;/a&gt;, a small, lightweight, affordable board with onboard WiFi. Adafruit have written some &lt;a href="https://learn.adafruit.com/adafruit-feather-huzzah-esp8266?view=all#using-arduino-ide"&gt;great step by step instructions on how to get started with this board&lt;/a&gt; and the &lt;a href="https://www.arduino.cc/en/Main/Software"&gt;Arduino IDE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Huzzah runs C/C++ out of the box and the Arduino IDE comes with a collection of example code to get you up and running. Here’s an example of how to set all of the lights in the array to show red:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You’ll need to do a little more soldering to connect the Huzzah up to the LED array. As seen above, we’re putting the data out on pin 4 and we’ll be using a USB battery, so you’ll want to connect your pins up as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FL-cKi2j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AKqnT5CtZhMsX_-7A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FL-cKi2j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AKqnT5CtZhMsX_-7A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Board&lt;/strong&gt;⠀⠀⠀** &lt;strong&gt;⠀&lt;/strong&gt;LEDs**&lt;br&gt;
Pin 4⠀⠀ ↔ ⠀Data&lt;br&gt;
GND⠀ ⠀↔ ⠀⠀&lt;strong&gt;−&lt;/strong&gt;&lt;br&gt;
USB ⠀ ⠀↔⠀ +5V&lt;/p&gt;

&lt;p&gt;Connect up the board and run the above code to see your LEDs light up!&lt;/p&gt;
&lt;h2&gt;
  
  
  Making a web app to set the colours on the array
&lt;/h2&gt;

&lt;p&gt;So, now we have a display, and a way of controlling it, we need a way to tell the controller what colours to set. Being a web developer, I opted to build a web app. It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WSQJKsCx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2ArMpe6MpklsURepX6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WSQJKsCx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2ArMpe6MpklsURepX6" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app has some buttons at the top where the user can pick a colour, and an SVG of squares that represent the pixels in the array.&lt;/p&gt;

&lt;p&gt;Each square in the SVG has an id with its number in the array — 0, 1, 2, 3, etc etc&lt;/p&gt;

&lt;p&gt;Once a colour and a square has been selected by the user; the app updates the colour of that square to match the selected colour.&lt;/p&gt;

&lt;p&gt;You can check out the app and its code here: &lt;a href="https://ably-mqtt-iotshirt.glitch.me/"&gt;https://ably-mqtt-iotshirt.glitch.me/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As well as updating the display of the app, we want a selected square to illuminate the correct LED on the array. We can do that using a &lt;a href="https://www.ably.io/"&gt;**realtime data service&lt;/a&gt; &lt;strong&gt;and the pub/sub model. We’ll create a &lt;a href="https://www.ably.io/documentation/realtime/channels"&gt;data channel&lt;/a&gt; on which we will which **publish&lt;/strong&gt; messages that contain the colour updates. We can also &lt;strong&gt;subscribe&lt;/strong&gt; to this channel to listen for changes made by other people also using the web app.&lt;/p&gt;

&lt;p&gt;To achieve this, I used &lt;a href="https://www.ably.io/channels"&gt;Ably’s messaging platform&lt;/a&gt; to set me up with a channel. Then I used their &lt;a href="https://github.com/ably/ably-js"&gt;JavaScript SDK&lt;/a&gt; to manage publishing and subscribing to messages on this channel. In order to use the SDK, you’ll need an API key which you can get with a &lt;a href="https://www.ably.io/signup"&gt;free account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the front end of the app I use the following code to set up my channel and subscribe to it:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The cool thing about using realtime pub/sub streaming is that all of the other users of the web app are also subscribed to these messages, so their clients will update when people paint cooperatively with them!&lt;/p&gt;

&lt;p&gt;We’re using the Ably SDK’s &lt;code&gt;createTokenRequest&lt;/code&gt; feature to authenticate to be allowed to connect to the data stream. On the back end, in a node server we will require the &lt;a href="https://github.com/ably/ably-js"&gt;Ably promises SDK&lt;/a&gt; to do our authentication and to create &lt;a href="https://www.ably.io/documentation/rest-api/token-request-spec"&gt;Ably Token Request objects&lt;/a&gt;. Using tokens instead of sending the API key directly minimises the amount of work that our server needs to do and keeps our API keys nice and safe.&lt;/p&gt;

&lt;p&gt;Now that we’ve established a connection to a channel, and subscribed to it, we need to publish a message to that channel when someone clicks on a square. We add a event listener for a click event to each of our squares:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When clicked, we’ll publish a message to the “tshirt” channel with the number of the pixel to change and the RGB value of the selected colour. An example of the data sent would look like this:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**name:** tshirt
**data:** 001#aa00ff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where we’ve clicked the second square in the array and selected a purple colour.&lt;/p&gt;
&lt;h2&gt;
  
  
  Receiving data on the Huzzah
&lt;/h2&gt;

&lt;p&gt;Now that we’re subscribed to and publishing to the channel in the web app, we need a way to get the data from that to the Huzzah board.&lt;/p&gt;

&lt;p&gt;You are probably familiar with HTTP — the protocol that browsers use to request web pages. The word “protocol” just means “the way two things talk to each other”. HTTP is great, it powers websites, and APIs and is built to be descriptive and flexible, and it &lt;strong&gt;can&lt;/strong&gt; be used for IoT connections, but it’s not lightweight and it’s not terribly fast. Another problem with HTTP is that it’s pull only, the device has to constantly connect and ask “Are there any updates?” “What about now?” “Anything now?” which is both data and time consuming.&lt;/p&gt;

&lt;p&gt;On low power IoT devices, we don’t have much memory, power or bandwidth, so we need protocols that are designed to be small and fast. Message Queuing Telemetry Transport —&lt;a href="http://mqtt.org/"&gt; MQTT&lt;/a&gt; is a connectivity protocol which was designed to be extremely lightweight. Connecting to a server only takes about 80 bytes and the device stays connected the entire time. Data is &lt;strong&gt;published&lt;/strong&gt; when it is pushed from the device to the server and the device &lt;strong&gt;subscribes&lt;/strong&gt; to data pushed from the server. Because the size of the data being sent over MQTT is small by design, messages can be sent quickly, making the hardware very responsive. Making it feasible to change the lights on the tshirt in realtime!&lt;/p&gt;

&lt;p&gt;In order to use MQTT, we’ll need an MQTT broker. This is just a server that the devices connect to using the MQTT protocol in order to listen for messages. It keeps track of all the clients that are connected and the topics they are subscribed to, forwarding any messages to any subscribers. For this project I am using &lt;a href="https://www.ably.io/documentation/mqtt"&gt;mqtt.ably.io&lt;/a&gt; as my broker. A third party that your IoThings can connect to to send and receive messages.&lt;/p&gt;

&lt;p&gt;The cool thing about the &lt;a href="https://www.ably.io/documentation/mqtt"&gt;Ably MQTT broker&lt;/a&gt; is that any messages we send in our browser to an Ably Channel using the JavaScript SDK, are also sent out over MQTT automatically, so we have no extra setup to do!&lt;/p&gt;

&lt;p&gt;The code to get our Huzzah board up and running with MQTT is as follows:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Every time &lt;code&gt;ensure_MQTT_connected&lt;/code&gt; is called, if the client is already connected, it returns immediately, otherwise, it’ll loop until it can establish a connection — subscribing when it does. This function is called every time the hardware runs its main loop to make sure it doesn’t accidentally disconnect from the MQTT broker due to our internet connection dropping. The &lt;code&gt;process_messages&lt;/code&gt; function calls the client’s &lt;code&gt;loop&lt;/code&gt; function. This function is part of the MQTT library, it calls the callback to get any messages that have arrived in the MQTT buffer since the last time it was called.&lt;/p&gt;

&lt;p&gt;If we take the example message we used earlier we can look at how we will process it once received by the board:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The final thing to mention is the order of the lights in the array. You may have noticed that when I wired up the lights, I wanted to keep my wires nice and neat, so i soldered these nice little jumps at each end. But this means that the data line doesn’t run from left to right as would in normal array enumeration - it runs in what I have affectionately called ‘Snake mode’. :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9bXLCGZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A6Pi57mkkGXT8sVmwFNxv4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9bXLCGZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A6Pi57mkkGXT8sVmwFNxv4w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not only that, i connected up the cable at the wrong end of the first strip, all of this means that we have to essentially reverse every other line of the array in order for the numbering of the LEDs to match that of the array in the app. Oops! The code to do this is as follows:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The code to run an array of neopixels wired up in this configuration is now available as an &lt;a href="https://www.arduinolibraries.info/libraries/snakelights"&gt;Arduino library&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The IoTshirt in action
&lt;/h2&gt;

&lt;p&gt;Here are some photos of the t-shirt&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e7mFGqqw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AdfuLpaYiccyvc797ZUYBNA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e7mFGqqw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AdfuLpaYiccyvc797ZUYBNA.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uzCfUliC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AhaaANAZdYu_Alkl-Ct7JUw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uzCfUliC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AhaaANAZdYu_Alkl-Ct7JUw.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iP6tz482--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ANj8yVcyBS13gQ-RqoDdhig.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iP6tz482--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ANj8yVcyBS13gQ-RqoDdhig.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lkNy9weS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AtzHnNczibgTbSr-GQ4kw7A.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lkNy9weS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AtzHnNczibgTbSr-GQ4kw7A.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1)⠀The t-shirt with the array on the front (and me pointing at it)&lt;br&gt;
2)⠀An example of drawing on the t-shirt using the app, I drew a heart.&lt;br&gt;
3)⠀People using the app in realtime, they created a beautiful &lt;a href="https://www.tate.org.uk/tate-etc/issue-39-spring-2017/private-view-sam-riviere-on-eduardo-paolozzi"&gt;Paolozzi&lt;/a&gt; like piece!&lt;br&gt;
4+5)⠀A message I wrote on the app and it showing on the tshirt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?!
&lt;/h2&gt;

&lt;p&gt;I’d love to take this idea further and perhaps make a wearable game, something like a tamagotchi or digimon, which would require a slightly larger pixel density, and therefore a new pixel array. I’d also love to get some animations running on the t-shirt. If you’ve got any ideas for how I can develop this further or some fun demos we could run on the t-shirt, do let me know! I’d love to work with others on this project.&lt;/p&gt;

&lt;p&gt;All of the code for the t-shirt and the app is opensource and available on &lt;em&gt;GitHub&lt;/em&gt;: &lt;a href="https://github.com/thisisjofrank/interactive-lights"&gt;https://github.com/thisisjofrank/interactive-lights&lt;/a&gt;&lt;br&gt;
you can see and use the app on&lt;br&gt;
&lt;em&gt;Glitch&lt;/em&gt;: &lt;a href="https://ably-mqtt-iotshirt.glitch.me/"&gt;https://ably-mqtt-iotshirt.glitch.me/&lt;/a&gt; and it’s code is&lt;br&gt;
&lt;em&gt;remixable&lt;/em&gt;: &lt;a href="https://glitch.com/~ably-mqtt-iotshirt"&gt;https://glitch.com/~ably-mqtt-iotshirt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this long, rambling blog gives you some starting points for hardware projects and realtime data. Let me know if you make your own or if this inspires you to build other projects, I’d love to see them!&lt;/p&gt;

</description>
      <category>iot</category>
      <category>javascript</category>
      <category>wearables</category>
      <category>realtimedata</category>
    </item>
  </channel>
</rss>
