<?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: JSLab</title>
    <description>The latest articles on DEV Community by JSLab (@jslab).</description>
    <link>https://dev.to/jslab</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4197125%2Faef89c26-9d02-4c49-918d-c1eadb90d1c7.png</url>
      <title>DEV Community: JSLab</title>
      <link>https://dev.to/jslab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jslab"/>
    <language>en</language>
    <item>
      <title>Multi-cursor moves that replace a regex</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Thu, 12 Nov 2026 02:02:29 +0000</pubDate>
      <link>https://dev.to/jslab/multi-cursor-moves-that-replace-a-regex-eap</link>
      <guid>https://dev.to/jslab/multi-cursor-moves-that-replace-a-regex-eap</guid>
      <description>&lt;p&gt;You paste ten lines from a spreadsheet and need them as a JavaScript array. You could write a regex. Or you could edit all ten lines at once, which is faster and you can see it working.&lt;/p&gt;

&lt;p&gt;Every Monaco-based editor (VS Code included) has these. The shortcuts below are JSLab's; your editor's may differ, and its command palette will tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move 1: a pasted column into an array
&lt;/h2&gt;

&lt;p&gt;You paste this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ada Lovelace
Alan Turing
Grace Hopper
Katherine Johnson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Select all four lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split Selection into Lines&lt;/strong&gt; (⇧⌘L): now there's a cursor at the end of each line.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Home&lt;/strong&gt;: every cursor jumps to the start of its line. Type &lt;code&gt;"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;End&lt;/strong&gt;: type &lt;code&gt;",&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;"Ada Lovelace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"Alan Turing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"Grace Hopper"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;"Katherine Johnson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap it in &lt;code&gt;const names = [&lt;/code&gt; … &lt;code&gt;];&lt;/code&gt; and you're done. Four lines or four hundred, it's the same four steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move 2: rename by selecting the next match
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&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="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the caret in &lt;code&gt;price&lt;/code&gt; and press &lt;strong&gt;⌘D&lt;/strong&gt; (Select Next Occurrence) once for each match you want. Type &lt;code&gt;cost&lt;/code&gt;, and they all change.&lt;/p&gt;

&lt;p&gt;Unlike find and replace, you see each match get picked, and you can stop before the one you don't want. Here, the arrow function's parameter is a different variable that happens to share the name. ⌘D lets you include it or skip it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move 3: column cursors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Add Cursor Below&lt;/strong&gt; (⌃⇧↓) stacks cursors straight down, one per line. Good for adding the same thing at the same column, like a key prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;REGION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"eu"&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;Cursor before &lt;code&gt;URL&lt;/code&gt;, ⌃⇧↓ twice, type &lt;code&gt;API_&lt;/code&gt;, and all three keys get the prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expand Selection&lt;/strong&gt; (⇧⌘Space) grows the selection to the next enclosing thing: word, string, argument list, block. Faster than dragging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate Line&lt;/strong&gt; (⇧⌘D) and &lt;strong&gt;Move Line Up / Down&lt;/strong&gt; (⌃⌘↑ / ⌃⌘↓) rearrange code without cut and paste.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you think in Vim
&lt;/h2&gt;

&lt;p&gt;JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, has &lt;strong&gt;Vim Keys&lt;/strong&gt; in Settings → Editor, and &lt;strong&gt;⌘R still runs your code in every mode&lt;/strong&gt;, so you never have to leave Normal mode to see a result.&lt;/p&gt;

&lt;p&gt;In a scratchpad, multi-cursor matters more than in a project: you're constantly reshaping pasted data into something you can run. With Auto Run on, the result updates as soon as you stop typing, so you see the array form as you edit.&lt;/p&gt;

&lt;p&gt;Every one of these is a JSLab command, so you can rebind it in Settings → Keybindings or find it by name with ⇧⌘P.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. Editing shortcuts: &lt;a href="https://js-lab.app/docs/reference/keyboard-shortcuts" rel="noopener noreferrer"&gt;https://js-lab.app/docs/reference/keyboard-shortcuts&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>javascript</category>
      <category>vscode</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Tabs, files and session restore</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Thu, 12 Nov 2026 00:45:55 +0000</pubDate>
      <link>https://dev.to/jslab/tabs-files-and-session-restore-3213</link>
      <guid>https://dev.to/jslab/tabs-files-and-session-restore-3213</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/first-tab" rel="noopener noreferrer"&gt;Your first tab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A scratchpad lives or dies on one question: is my stuff still there tomorrow? In JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, the answer is yes, and this note covers how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session restore
&lt;/h2&gt;

&lt;p&gt;Quit JSLab and relaunch, and it brings back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your tabs and their code;&lt;/li&gt;
&lt;li&gt;each tab's layout, folds, cursor and scroll position;&lt;/li&gt;
&lt;li&gt;the window's size and the display it was on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also kept per tab: its working directory and whether its Web View is showing. Each tab's AI Chat conversation comes back too.&lt;/p&gt;

&lt;p&gt;The session lives in &lt;code&gt;session.json&lt;/code&gt; in JSLab's data folder. If that file can't be read, JSLab restores it from its backup when it can and tells you. A tab whose contents can't be read opens empty and &lt;strong&gt;read-only&lt;/strong&gt;, so JSLab never saves over the file on disk.&lt;/p&gt;

&lt;p&gt;Updates use the same mechanism: restarting to update relaunches JSLab with your tabs as they were.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;To&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open a blank tab&lt;/td&gt;
&lt;td&gt;⌘T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pick what a new tab is for&lt;/td&gt;
&lt;td&gt;+ in the tab bar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close the tab&lt;/td&gt;
&lt;td&gt;⌘W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reopen the tab you closed&lt;/td&gt;
&lt;td&gt;⇧⌘T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close every other tab&lt;/td&gt;
&lt;td&gt;⌥⌘T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jump to a tab&lt;/td&gt;
&lt;td&gt;⌘1 to ⌘8, ⌘9 for the last&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Drag to reorder. Closing the last tab opens a fresh one instead of quitting. A scratch tab is titled from its first line of code; &lt;strong&gt;Rename…&lt;/strong&gt; sets your own title.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;+&lt;/strong&gt; button asks what the tab is for: Plain JavaScript, Plain TypeScript, React, Vue, Solid or Angular. That sets the language, runtime and framework, installs what's needed and opens starter code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Files
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Open… (⌘O)&lt;/strong&gt;, &lt;strong&gt;Save (⌘S)&lt;/strong&gt; and &lt;strong&gt;Save As… (⇧⌘S)&lt;/strong&gt; work with &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, &lt;code&gt;.mjs&lt;/code&gt;, &lt;code&gt;.cjs&lt;/code&gt;, &lt;code&gt;.mts&lt;/code&gt;, &lt;code&gt;.cts&lt;/code&gt;, &lt;code&gt;.json&lt;/code&gt; and &lt;code&gt;.txt&lt;/code&gt;. Drop files on the window and each opens in a new tab. From a terminal, &lt;code&gt;jslab file.ts&lt;/code&gt; does the same.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrvngn1igjmd150ygy73.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrvngn1igjmd150ygy73.png" alt="A JSLab window with three tabs open, &amp;quot;Format a relative date&amp;quot;, &amp;quot;Retry with backoff&amp;quot; and &amp;quot;Order totals by region&amp;quot;, each titled from its first line of code, above the start of a TypeScript orders example." width="720" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few guard rails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSLab asks before it opens or pastes more than 5 MB, and won't open a file over 50 MB.&lt;/li&gt;
&lt;li&gt;Closing a tab with unsaved changes asks whether to save. &lt;strong&gt;Confirm Close&lt;/strong&gt; (Settings → General) asks before closing any tab.&lt;/li&gt;
&lt;li&gt;A saved tab's right-click menu has &lt;strong&gt;Copy Path&lt;/strong&gt; and &lt;strong&gt;Reveal in Finder&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JSLab launches Nov 15. The full page: &lt;a href="https://js-lab.app/docs/first-tab" rel="noopener noreferrer"&gt;https://js-lab.app/docs/first-tab&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>productivity</category>
      <category>macos</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Prettier on demand, on run, on save</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Wed, 11 Nov 2026 00:47:33 +0000</pubDate>
      <link>https://dev.to/jslab/prettier-on-demand-on-run-on-save-m57</link>
      <guid>https://dev.to/jslab/prettier-on-demand-on-run-on-save-m57</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/formatting" rel="noopener noreferrer"&gt;Formatting&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Scratch code is written fast and formatted never. JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, formats with Prettier, so the mess you typed at speed can look like code before you paste it anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  On demand
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;⌥⇧F (Format Code)&lt;/strong&gt; formats the current tab. It's also in Actions → Format Code and the command palette.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;items&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="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"A-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"B-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;qty&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="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;paid&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty&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="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One ⌥⇧F later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;items&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="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"A-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"B-7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&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="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;paid&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty&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="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format lands as &lt;strong&gt;one undo step&lt;/strong&gt;, and JSLab applies minimal edits instead of replacing the whole buffer, so your folds, scroll position and cursor stay where they were.&lt;/p&gt;

&lt;h2&gt;
  
  
  On run, on save
&lt;/h2&gt;

&lt;p&gt;Two settings, both off by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format on Run&lt;/strong&gt; (Settings → General) formats before each run, unless you're typing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format on Save&lt;/strong&gt; (Settings → Editor) formats when you save a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Settings → Formatting&lt;/strong&gt; holds every Prettier option JSLab supports, with Prettier's usual defaults:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Print Width&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tab Width&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semicolons&lt;/td&gt;
&lt;td&gt;On&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single Quotes&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trailing Commas&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrow Parens&lt;/td&gt;
&lt;td&gt;Always&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus Use Tabs, Quote Props, JSX Single Quotes, Bracket Spacing and Bracket Same Line. Print Width goes from 20 to 320, and Tab Width from 1 to 16.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it can't parse
&lt;/h2&gt;

&lt;p&gt;If Prettier can't parse the tab, the status bar says &lt;strong&gt;Couldn't format&lt;/strong&gt;, with Prettier's message, and your code is left as it was. A format that takes too long is stopped and the formatter restarts, so a giant paste can't hang the editor.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The full page: &lt;a href="https://js-lab.app/docs/formatting" rel="noopener noreferrer"&gt;https://js-lab.app/docs/formatting&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>prettier</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What your TypeScript becomes when it runs</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Tue, 10 Nov 2026 16:00:54 +0000</pubDate>
      <link>https://dev.to/jslab/what-your-typescript-becomes-when-it-runs-5ei3</link>
      <guid>https://dev.to/jslab/what-your-typescript-becomes-when-it-runs-5ei3</guid>
      <description>&lt;p&gt;TypeScript has a simple story: types are checked, then erased, and what runs is JavaScript. That's true for almost every line you write. The exceptions are worth knowing, because they're the lines that behave differently from how they look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that disappears
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&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;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"./config"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&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="nb"&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="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;User&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="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dark&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="k"&gt;satisfies&lt;/span&gt; &lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`Hi ${u.name}`&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;Run it through any TypeScript compiler and you get, give or take whitespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&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="n"&gt;raw&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="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dark&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&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="sb"&gt;`Hi ${u.name}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface, the &lt;code&gt;import type&lt;/code&gt;, the annotations, &lt;code&gt;as&lt;/code&gt; and &lt;code&gt;satisfies&lt;/code&gt;: all gone. None of it exists at runtime, which is why &lt;code&gt;as User[]&lt;/code&gt; can't check anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that doesn't
&lt;/h2&gt;

&lt;p&gt;A few TypeScript features generate JavaScript of their own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enums&lt;/strong&gt; become an object, built by a small function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;High&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;turns into roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Low"&lt;/span&gt;&lt;span class="p"&gt;]&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="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Low"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"High"&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"High"&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="nc"&gt;Level&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Level&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="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;That's why &lt;code&gt;Level[0]&lt;/code&gt; is &lt;code&gt;"Low"&lt;/code&gt;: a numeric enum maps both ways. The exact shape varies by compiler; the idea doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter properties&lt;/strong&gt; become assignments:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Point&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="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&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="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&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;&lt;strong&gt;Namespaces&lt;/strong&gt; with values become an object built by a function too, like enums.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Node can run &lt;code&gt;.ts&lt;/code&gt; files by stripping types, without a compile step. Stripping only works for syntax that can be erased. An enum or a parameter property needs code generated, so Node's default mode refuses it.&lt;/p&gt;

&lt;p&gt;TypeScript 5.8 added a flag for exactly this: &lt;code&gt;erasableSyntaxOnly&lt;/code&gt; reports an error on enums, parameter properties and value namespaces, so your code stays strippable. The usual replacement for an enum is a plain object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Low&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="nc"&gt;High&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="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seeing it for your own code
&lt;/h2&gt;

&lt;p&gt;You learn this fastest by looking at the output. I do it in JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS: &lt;strong&gt;Actions → Show Transpiled Output&lt;/strong&gt; puts the JavaScript your tab actually runs in the side bar, next to your code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It refreshes on each run, and marks itself &lt;strong&gt;Stale&lt;/strong&gt; once you've edited past the run it shows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide instrumentation&lt;/strong&gt; switches between the code exactly as it runs and the code without JSLab's own additions for Auto Log, logpoints and loop protection. Turn it off once; it's a good look at how those features work.&lt;/li&gt;
&lt;li&gt;Which syntax proposals the transform accepts, such as the pipeline operator, is set in &lt;strong&gt;Settings → Build&lt;/strong&gt;. Decorators work as they are.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Types are checked in the editor and stripped when the code runs, so a type error is an underline, never a failed run.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. Show Transpiled Output: &lt;a href="https://js-lab.app/docs/editor" rel="noopener noreferrer"&gt;https://js-lab.app/docs/editor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Prototype an API call before you write the client</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Tue, 10 Nov 2026 04:04:42 +0000</pubDate>
      <link>https://dev.to/jslab/prototype-an-api-call-before-you-write-the-client-2poi</link>
      <guid>https://dev.to/jslab/prototype-an-api-call-before-you-write-the-client-2poi</guid>
      <description>&lt;p&gt;Before writing a client for an API, I want answers: what does the response actually look like, what happens without a token, what are the limits? A scratch file gets those answers faster than reading the docs twice.&lt;/p&gt;

&lt;p&gt;Our test subject: the GitHub REST API, which works without a token (with a low rate limit) and with one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: the token comes from the environment
&lt;/h2&gt;

&lt;p&gt;Never paste a token into code, even scratch code. It ends up in a screenshot, a gist or a commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Node and Bun, &lt;code&gt;process.env&lt;/code&gt; holds the environment the process started with. &lt;code&gt;undefined&lt;/code&gt; here is a valid state, and the code should handle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: a request that fails properly
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&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="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/repos/oven-sh/bun"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"application/vnd.github+json"&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Bearer ${token}`&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="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="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&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="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="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`GitHub ${res.status}: ${await res.text()}`&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;Three habits in there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conditional header.&lt;/strong&gt; The spread adds &lt;code&gt;Authorization&lt;/code&gt; only when there's a token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A timeout.&lt;/strong&gt; &lt;code&gt;fetch&lt;/code&gt; has none by default. &lt;code&gt;AbortSignal.timeout(5000)&lt;/code&gt; aborts after five seconds with a &lt;code&gt;TimeoutError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;res.ok&lt;/code&gt; is your job.&lt;/strong&gt; &lt;code&gt;fetch&lt;/code&gt; only rejects on network failure. A 404 or 403 resolves happily, so throw on it yourself, with the body: GitHub's error messages are good.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: type what you actually got
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;open_issues_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;pushed_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the type from the real response, not from memory. And remember &lt;code&gt;as&lt;/code&gt; is a promise to the compiler, not a check. For anything beyond a prototype, validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: read the limits
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="s2"&gt;"x-ratelimit-limit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="s2"&gt;"x-ratelimit-remaining"&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;Run it without a token and with one, and compare. That's the number your client's retry logic has to respect.&lt;/p&gt;

&lt;h2&gt;
  
  
  In a scratchpad
&lt;/h2&gt;

&lt;p&gt;I do this in JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, in a Bun tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The token goes in &lt;strong&gt;Tools → Environment Variables…&lt;/strong&gt;. Values are masked on screen, they're saved in a file only your user account can read, and every run sees them in &lt;code&gt;process.env&lt;/code&gt;. Or point the tab at your project's folder with &lt;strong&gt;Set Working Directory…&lt;/strong&gt; and JSLab loads that folder's &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Top-level &lt;code&gt;await&lt;/code&gt; just works, and &lt;strong&gt;Auto Log&lt;/strong&gt; shows &lt;code&gt;repo.full_name&lt;/code&gt;, the star count and both headers beside their lines without a single &lt;code&gt;console.log&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Response&lt;/code&gt; and its &lt;code&gt;Headers&lt;/code&gt; are shown in their own form when logged, so &lt;code&gt;res&lt;/code&gt; on its own line is worth a look too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Need to hit an API that doesn't send CORS headers, from code that also touches the DOM? The &lt;strong&gt;Browser &amp;amp; Node APIs&lt;/strong&gt; runtime has a &lt;code&gt;fetch&lt;/code&gt; with no CORS restriction. The plain &lt;strong&gt;Browser&lt;/strong&gt; runtime keeps real CORS, so you can check what a website would see.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. Environment variables: &lt;a href="https://js-lab.app/docs/environment-variables" rel="noopener noreferrer"&gt;https://js-lab.app/docs/environment-variables&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>api</category>
      <category>bunjs</category>
    </item>
    <item>
      <title>The jslab CLI: open files and pipe code into a tab</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Tue, 10 Nov 2026 00:45:57 +0000</pubDate>
      <link>https://dev.to/jslab/the-jslab-cli-open-files-and-pipe-code-into-a-tab-3lie</link>
      <guid>https://dev.to/jslab/the-jslab-cli-open-files-and-pipe-code-into-a-tab-3lie</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/reference/cli" rel="noopener noreferrer"&gt;The jslab CLI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;JSLab is a JavaScript &amp;amp; TypeScript scratchpad for macOS, which makes it a GUI app. But a lot of the code you want to try starts in a terminal: a file in a repo, the output of another command, a one-liner. &lt;code&gt;jslab&lt;/code&gt; is a small command-line tool that gets it into a tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Help → Install jslab Command…&lt;/strong&gt;. JSLab links its own binary into &lt;code&gt;~/.local/bin/jslab&lt;/code&gt;. If that folder isn't on your &lt;code&gt;PATH&lt;/code&gt;, the message gives you the line to add to your shell profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="kd"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$HOME/.local/bin:$PATH"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's installed, the same menu item reads &lt;strong&gt;Uninstall jslab Command…&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jslab notes.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file opens in a new tab. The tab's language comes from the extension, or from your settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipe code in, and run it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;-&lt;/code&gt; reads code from stdin. &lt;code&gt;--run&lt;/code&gt; runs it as soon as it opens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'console.log(new Date())'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jslab --run -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;--run&lt;/code&gt;, the code only opens. For &lt;code&gt;-&lt;/code&gt;, the tab's working directory defaults to your terminal's current folder, so relative paths in the piped code resolve where you are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jslab [file ...]                 Open files in new tabs
jslab -                          Read code from stdin into a new tab
jslab --run [file|-]             Open and run immediately
      --runtime bun|browser|browser-node
      --lang ts|js|tsx|jsx
      --cwd &amp;lt;dir&amp;gt;
      --title &amp;lt;title&amp;gt;
jslab --version | --help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;browser-node&lt;/code&gt; is the Browser &amp;amp; Node APIs runtime. So opening a component in a browser tab, in the current folder, is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jslab --runtime browser --cwd . app.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two I use
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Last week's version of a file, in its own tab&lt;/span&gt;
git show HEAD~5:src/format.ts &lt;span class="o"&gt;|&lt;/span&gt; jslab --lang ts -

&lt;span class="c"&gt;# Try a snippet you just copied from a README&lt;/span&gt;
pbpaste &lt;span class="o"&gt;|&lt;/span&gt; jslab --run -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code from stdin has no file extension, so pass &lt;code&gt;--lang&lt;/code&gt; when it's TypeScript. &lt;code&gt;pbpaste&lt;/code&gt; prints the macOS clipboard, so the second line runs whatever you copied. Read it before you pipe it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;If JSLab isn't running, &lt;code&gt;jslab&lt;/code&gt; starts it and waits up to 10 seconds for it to be ready. Each call then sends one request over a local socket, &lt;code&gt;jslab.sock&lt;/code&gt;, in JSLab's data folder.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The full page: &lt;a href="https://js-lab.app/docs/reference/cli" rel="noopener noreferrer"&gt;https://js-lab.app/docs/reference/cli&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>cli</category>
      <category>terminal</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Snippets (Cmd+B) and the command palette</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Mon, 09 Nov 2026 00:48:32 +0000</pubDate>
      <link>https://dev.to/jslab/snippets-b-and-the-command-palette-k74</link>
      <guid>https://dev.to/jslab/snippets-b-and-the-command-palette-k74</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/snippets" rel="noopener noreferrer"&gt;Snippets&lt;/a&gt; and &lt;a href="https://js-lab.app/docs/keybindings" rel="noopener noreferrer"&gt;the command palette&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two features in JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, that are about not typing the same thing twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snippets: a name is a trigger
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;⌘B&lt;/strong&gt; (or Tools → Snippets…, or the activity bar) opens the Snippets panel in the side bar. Search by name or description, then &lt;strong&gt;Insert&lt;/strong&gt; a snippet at the cursor, &lt;strong&gt;Insert in New Tab&lt;/strong&gt;, &lt;strong&gt;Copy&lt;/strong&gt;, &lt;strong&gt;Edit&lt;/strong&gt; or &lt;strong&gt;Delete&lt;/strong&gt; it.&lt;/p&gt;

&lt;p&gt;The part I use most: &lt;strong&gt;a snippet's name is its trigger&lt;/strong&gt;. Type the name in the editor, press ⇥, and it expands. Names show up in autocomplete too. Tab only expands when a snippet's name sits right before the caret; everywhere else it indents as usual.&lt;/p&gt;

&lt;p&gt;To make one, select some code and choose &lt;strong&gt;Edit → Create Snippet…&lt;/strong&gt; (it's in the right-click menu too). With nothing selected, it starts from the whole tab. Each snippet has a &lt;strong&gt;Name&lt;/strong&gt; (letters, digits, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt;, unique), an optional &lt;strong&gt;Description&lt;/strong&gt;, a &lt;strong&gt;Language hint&lt;/strong&gt; and the &lt;strong&gt;Body&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Placeholders
&lt;/h2&gt;

&lt;p&gt;A body can carry tab stops. After inserting, ⇥ jumps between them:&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;$&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;name&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;… are tab stops in order, &lt;code&gt;${1:name}&lt;/code&gt; has default text, and &lt;code&gt;$0&lt;/code&gt; is where the cursor ends. A body with no placeholders is inserted exactly as written, &lt;code&gt;$&lt;/code&gt; and all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import and export
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools → Import Snippets…&lt;/strong&gt; and &lt;strong&gt;Export Snippets…&lt;/strong&gt; move your whole library as one JSON file (&lt;code&gt;"format": "jslab-snippets"&lt;/code&gt;). Importing merges by name, ignoring case, and asks &lt;strong&gt;Overwrite&lt;/strong&gt;, &lt;strong&gt;Keep Both&lt;/strong&gt; or &lt;strong&gt;Skip&lt;/strong&gt; on a clash. Imported snippets always get new ids, so a file can't overwrite one of yours by claiming its id.&lt;/p&gt;

&lt;p&gt;One warning straight from the docs: an inserted snippet is live code. With Auto Run on, it runs like anything else you type, including one from someone else's library.&lt;/p&gt;

&lt;h2&gt;
  
  
  The command palette
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;⇧⌘P&lt;/strong&gt; (View → Command Palette…) searches every command by name.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nnb68ow7lnbfka3ig9i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nnb68ow7lnbfka3ig9i.png" alt="The JSLab command palette with &amp;quot;tog&amp;quot; typed: matching letters highlighted, results grouped under Edit and View, Toggle Magic Comment with ⌥⇧⌘/, and toggles marked &amp;quot;currently on&amp;quot; or &amp;quot;currently off&amp;quot;." width="616" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matched letters are highlighted, and results are grouped by section.&lt;/li&gt;
&lt;li&gt;Each result shows its shortcut, so the palette teaches you the keys as you go.&lt;/li&gt;
&lt;li&gt;Toggle commands show whether they're on or off.&lt;/li&gt;
&lt;li&gt;A command you can't use right now stays listed as &lt;strong&gt;Unavailable now&lt;/strong&gt;, so you always know it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Type a theme's name and the palette switches themes too. ↑ ↓ to move, ↵ to run, esc to close.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The full pages: &lt;a href="https://js-lab.app/docs/snippets" rel="noopener noreferrer"&gt;https://js-lab.app/docs/snippets&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>macos</category>
    </item>
    <item>
      <title>Quick data munging: group, sum and bucket a CSV</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Sun, 08 Nov 2026 16:02:02 +0000</pubDate>
      <link>https://dev.to/jslab/quick-data-munging-group-sum-and-bucket-a-csv-46kb</link>
      <guid>https://dev.to/jslab/quick-data-munging-group-sum-and-bucket-a-csv-46kb</guid>
      <description>&lt;p&gt;Someone sends you a CSV and a question: "totals by region? and by week?" It's not worth a notebook, a database or a project. It's worth twenty lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`date,region,product,amount
2026-10-05,EU,Pro,120
2026-10-06,US,Basic,80
2026-10-06,EU,Basic,45.5
2026-10-12,APAC,Pro,210
2026-10-13,US,Pro,120`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: rows (the naive way)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;header&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&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;line&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;cells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&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;k&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="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cells&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine for this file. Hold that thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: totals by region with &lt;code&gt;Object.groupBy&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Object.groupBy&lt;/code&gt; (ES2024) does the bucketing that used to need a &lt;code&gt;reduce&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byRegion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byRegion&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;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rs&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;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&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;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&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="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { EU: 165.5, US: 200, APAC: 210 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;Number(...)&lt;/code&gt;: everything from a CSV is a string, and &lt;code&gt;"120" + "45.5"&lt;/code&gt; is not a sum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: use a real parser
&lt;/h2&gt;

&lt;p&gt;The naive split breaks on the first quoted field: &lt;code&gt;2026-10-07,EU,"Pro, annual",400&lt;/code&gt; has four commas. Don't fix that with a regex. Use a parser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Papa&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;papaparse&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="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;errors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Papa&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;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;header&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="na"&gt;dynamicTyping&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;// "120" → 120&lt;/span&gt;
  &lt;span class="na"&gt;skipEmptyLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;data&lt;/code&gt; is the same array of objects, with quoted fields handled and &lt;code&gt;amount&lt;/code&gt; already a number. Check &lt;code&gt;errors&lt;/code&gt; before you trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: by week, without the timezone bug
&lt;/h2&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;parseISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startOfWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&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;date-fns&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;weekOf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&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;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;startOfWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weekStartsOn&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;yyyy-MM-dd&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;byWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&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;weekOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byWeek&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;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rs&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;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&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;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&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="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// [{ week: "2026-10-05", total: 245.5 }, { week: "2026-10-12", total: 330 }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why &lt;code&gt;parseISO&lt;/code&gt; and not &lt;code&gt;new Date("2026-10-05")&lt;/code&gt;? A date-only string passed to &lt;code&gt;Date&lt;/code&gt; is parsed as &lt;strong&gt;UTC midnight&lt;/strong&gt;. West of Greenwich, that's still Sunday evening, and your Monday sale lands in the previous week. &lt;code&gt;parseISO&lt;/code&gt; reads it as local midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it in a scratchpad
&lt;/h2&gt;

&lt;p&gt;I do this kind of thing in JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⌘I&lt;/strong&gt; opens the package sheet. Add &lt;code&gt;papaparse&lt;/code&gt; and &lt;code&gt;date-fns&lt;/code&gt; once and every tab can import them. Or just write the import: JSLab underlines it and offers to install it as a quick fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Log&lt;/strong&gt; shows each step's value beside its line, so I see &lt;code&gt;totals&lt;/code&gt; without logging it.&lt;/li&gt;
&lt;li&gt;An array of records, like &lt;code&gt;data&lt;/code&gt; above, gets a &lt;strong&gt;Tree / Table&lt;/strong&gt; switch in the output. As a table I can sort it, filter it and &lt;strong&gt;copy it as CSV, JSON or Markdown&lt;/strong&gt;, which is usually what the person asking actually wanted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the real file, set the tab's working directory to its folder and read it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&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:fs/promises&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;csv&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;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./sales.csv&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;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSLab launches Nov 15. Tables and npm packages: &lt;a href="https://js-lab.app/docs/output" rel="noopener noreferrer"&gt;https://js-lab.app/docs/output&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>data</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Working directory and environment variables</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Sun, 08 Nov 2026 01:00:24 +0000</pubDate>
      <link>https://dev.to/jslab/working-directory-and-environment-variables-387o</link>
      <guid>https://dev.to/jslab/working-directory-and-environment-variables-387o</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/working-directory" rel="noopener noreferrer"&gt;Working directory&lt;/a&gt; and &lt;a href="https://js-lab.app/docs/environment-variables" rel="noopener noreferrer"&gt;Environment variables&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By default, a tab in JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, isn't tied to any folder. That's right for most snippets. It's wrong the moment you want to poke at a real project's files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give a tab a folder
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Actions → Set Working Directory…&lt;/strong&gt;, the working-directory button in the status bar, or &lt;strong&gt;Set Working Directory…&lt;/strong&gt; in the command palette. Each tab has its own, and it's kept across relaunches.&lt;/p&gt;

&lt;p&gt;With a working directory set, the tab runs as if it lived in that folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;process.cwd()&lt;/code&gt;, &lt;code&gt;__dirname&lt;/code&gt; and &lt;code&gt;import.meta.dir&lt;/code&gt; are that folder.&lt;/li&gt;
&lt;li&gt;Relative imports written as string literals resolve there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;folder&amp;gt;/node_modules&lt;/code&gt; is searched before JSLab's own installed packages, so you get the project's versions.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.env&lt;/code&gt; file in the folder is loaded into the run.
&lt;/li&gt;
&lt;/ul&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;readFile&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:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&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;./config.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// read from the working directory&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./README.md&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;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear it with &lt;strong&gt;Actions → Clear Working Directory&lt;/strong&gt;, or the × beside the folder's name in the status bar. If the folder is moved or deleted, the run says &lt;strong&gt;Working directory not found&lt;/strong&gt; and offers &lt;strong&gt;Change…&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables every tab sees
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools → Environment Variables…&lt;/strong&gt; (or the palette; there's no default shortcut) holds key and value pairs that every tab's run gets, on top of what your login shell exports.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Values are masked; &lt;strong&gt;Show&lt;/strong&gt; reveals one.&lt;/li&gt;
&lt;li&gt;Paste a whole &lt;code&gt;.env&lt;/code&gt; file into the Key field and JSLab fills in the rows for you to check.&lt;/li&gt;
&lt;li&gt;Changes apply from the next run.&lt;/li&gt;
&lt;li&gt;They're saved in &lt;code&gt;env.json&lt;/code&gt; in JSLab's data folder, readable only by your user account.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API_URL isn't set yet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JSLAB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "1" in every JSLab run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The order they apply in
&lt;/h2&gt;

&lt;p&gt;For a Bun run, later layers win:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your login shell's environment.&lt;/li&gt;
&lt;li&gt;The Environment Variables sheet.&lt;/li&gt;
&lt;li&gt;The working directory's &lt;code&gt;.env&lt;/code&gt;, if the tab has one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JSLAB=1&lt;/code&gt;, so your code can tell it's in JSLab.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the sheet holds what's true everywhere, and a project's &lt;code&gt;.env&lt;/code&gt; overrides it for the tabs pointed at that project.&lt;/p&gt;

&lt;p&gt;Two details from the docs: JSLab reads the &lt;code&gt;.env&lt;/code&gt; itself (up to 1 MiB) rather than leaving it to Bun, so every run gets the same result, and &lt;code&gt;BUN_OPTIONS&lt;/code&gt; never reaches your code.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The full pages: &lt;a href="https://js-lab.app/docs/working-directory" rel="noopener noreferrer"&gt;https://js-lab.app/docs/working-directory&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>bunjs</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Canvas sketches with no build step</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Sat, 07 Nov 2026 16:03:10 +0000</pubDate>
      <link>https://dev.to/jslab/canvas-sketches-with-no-build-step-201k</link>
      <guid>https://dev.to/jslab/canvas-sketches-with-no-build-step-201k</guid>
      <description>&lt;p&gt;Canvas is the most fun you can have with forty lines of JavaScript, and it needs nothing: no framework, no bundler, no dev server. Here's a sketch with the three details that make it look right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The page
&lt;/h2&gt;

&lt;p&gt;One HTML file. Open it in a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"c"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"320"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// the sketch goes here&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the script inline. From &lt;code&gt;file://&lt;/code&gt;, browsers block module scripts loaded with &lt;code&gt;src&lt;/code&gt;, so a second file means running a local server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detail 1: sharp on Retina
&lt;/h2&gt;

&lt;p&gt;A canvas is a bitmap. On a 2× display, a 320×200 canvas gets stretched and looks soft. Size the backing store in device pixels, then scale the drawing back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#c&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&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;dpr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&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="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&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;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&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;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you draw in CSS pixels and get device-pixel sharpness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detail 2: move by time, not by frame
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; runs at the display's refresh rate: 60, 120, sometimes less. Move things by &lt;code&gt;speed × elapsed time&lt;/code&gt; and they move at the same speed everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;x&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;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;y&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;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;:&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="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// pixels per second&lt;/span&gt;
  &lt;span class="na"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;:&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="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&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;dt&lt;/span&gt; &lt;span class="o"&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;min&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// seconds, capped&lt;/span&gt;
  &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rgba(15, 23, 42, 0.25)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// fade the last frame&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#f59e0b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &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;d&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;dots&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vx&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&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="nx"&gt;d&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="mi"&gt;3&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cap on &lt;code&gt;dt&lt;/code&gt; matters: switch tabs for a minute, come back, and an uncapped &lt;code&gt;dt&lt;/code&gt; teleports every dot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detail 3: fade, don't clear
&lt;/h2&gt;

&lt;p&gt;Painting a translucent rectangle instead of calling &lt;code&gt;clearRect&lt;/code&gt; leaves short trails. Change &lt;code&gt;0.25&lt;/code&gt; to &lt;code&gt;0.05&lt;/code&gt; for long ones, or &lt;code&gt;1&lt;/code&gt; for none. It's the cheapest effect in canvas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweaking it live
&lt;/h2&gt;

&lt;p&gt;The fun part of a sketch is changing numbers. With a plain HTML file, that's edit, switch windows, reload, repeat.&lt;/p&gt;

&lt;p&gt;In JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, I switch the tab to the &lt;strong&gt;Browser&lt;/strong&gt; runtime, and it renders into a &lt;strong&gt;Web View&lt;/strong&gt; tile under its output. The page starts empty, apart from a &lt;code&gt;&amp;lt;div id="root"&amp;gt;&lt;/code&gt;, so the first line makes the canvas:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;canvas id="c" width="320" height="200"&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the sketch stays exactly as above. With Auto Run, the sketch reruns a moment after I stop typing, so changing &lt;code&gt;120&lt;/code&gt; to &lt;code&gt;400&lt;/code&gt; is the whole loop.&lt;/p&gt;

&lt;p&gt;One thing I didn't expect to like: &lt;strong&gt;every run reloads the page&lt;/strong&gt;. In a hot-reload setup, it's easy to end up with two &lt;code&gt;requestAnimationFrame&lt;/code&gt; loops running at once after an edit. Here, each run starts from a clean page, so there's only ever one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;canvas.onclick&lt;/code&gt; with an &lt;code&gt;AudioContext&lt;/code&gt; works too, and while a tab is playing sound, a speaker icon on the tab mutes it.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The Web View docs: &lt;a href="https://js-lab.app/docs/web-view" rel="noopener noreferrer"&gt;https://js-lab.app/docs/web-view&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>canvas</category>
      <category>creativecoding</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop, Kill and loop protection</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Sat, 07 Nov 2026 00:46:07 +0000</pubDate>
      <link>https://dev.to/jslab/stop-kill-and-loop-protection-6c2</link>
      <guid>https://dev.to/jslab/stop-kill-and-loop-protection-6c2</guid>
      <description>&lt;p&gt;&lt;em&gt;JSLab feature notes: one docs page, explained. Today: &lt;a href="https://js-lab.app/docs/editor" rel="noopener noreferrer"&gt;Stop, Kill and loop protection&lt;/a&gt;, from The editor and live output.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, runs your code as you type it. Code you're typing is code that's half-written, and half-written code doesn't always end. This note covers what happens then.&lt;/p&gt;

&lt;h2&gt;
  
  
  A run can stay active on purpose
&lt;/h2&gt;

&lt;p&gt;A run doesn't end when the last line runs. It stays active while it has open timers, servers, sockets or child processes, and the status bar says so:&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;let&lt;/span&gt; &lt;span class="nx"&gt;ticks&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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;ticks&lt;/span&gt;&lt;span class="o"&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="s2"&gt;`tick &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ticks&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;ticks&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one ends itself after three ticks. One that doesn't is what Stop is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop and Kill
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⇧⌘R (Stop)&lt;/strong&gt; stops a run gracefully and closes its timers, servers and the rest. If the run doesn't respond, JSLab kills it after 500 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⌥⌘R (Kill)&lt;/strong&gt; kills it immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each run is its own process (or a freshly reloaded page in a browser tab), so killing it can't hurt the editor. &lt;code&gt;process.exit()&lt;/code&gt; flushes its output before the run ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a tab stops answering
&lt;/h2&gt;

&lt;p&gt;If your code stops responding altogether, JSLab shows &lt;strong&gt;This tab isn't responding&lt;/strong&gt;, with &lt;strong&gt;Kill&lt;/strong&gt; and &lt;strong&gt;Wait&lt;/strong&gt;. By default it appears after 3 seconds. Change that with &lt;strong&gt;Unresponsive Timeout&lt;/strong&gt; in Settings → Advanced, anywhere from 1,000 to 60,000 ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loop protection
&lt;/h2&gt;

&lt;p&gt;Most hangs in a scratchpad are a loop that never ends, so JSLab catches those before you have to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// i never reaches 10&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;do…while&lt;/code&gt;, &lt;code&gt;for…in&lt;/code&gt; and &lt;code&gt;for…of&lt;/code&gt; loops are stopped after too many iterations, with an error that names the loop's line.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loop Limit&lt;/strong&gt; (Settings → Advanced): 2,000 iterations by default, from 100 to 10,000,000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop Protection&lt;/strong&gt; in the same place turns it off, as does &lt;strong&gt;Toggle Loop Protection&lt;/strong&gt; in the command palette (⇧⌘P).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious how it works? &lt;strong&gt;Actions → Show Transpiled Output&lt;/strong&gt; shows the JavaScript your tab actually runs. Switch &lt;strong&gt;Hide instrumentation&lt;/strong&gt; off and you can see the loop-protection code JSLab adds around your loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe Mode
&lt;/h2&gt;

&lt;p&gt;If a tab hangs every time JSLab opens, hold &lt;strong&gt;Shift&lt;/strong&gt; while you launch it. JSLab starts in Safe Mode, with Auto Run paused, so you can fix the code before it runs. JSLab also reopens in Safe Mode by itself if it didn't shut down cleanly while running code.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. The full page: &lt;a href="https://js-lab.app/docs/editor" rel="noopener noreferrer"&gt;https://js-lab.app/docs/editor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>debugging</category>
      <category>macos</category>
    </item>
    <item>
      <title>Why a scratchpad should run your code in a separate process</title>
      <dc:creator>JSLab</dc:creator>
      <pubDate>Fri, 06 Nov 2026 16:02:28 +0000</pubDate>
      <link>https://dev.to/jslab/why-a-scratchpad-should-run-your-code-in-a-separate-process-b9</link>
      <guid>https://dev.to/jslab/why-a-scratchpad-should-run-your-code-in-a-separate-process-b9</guid>
      <description>&lt;p&gt;A scratchpad runs code you haven't finished writing. That's the job. Which means it runs a lot of code like this:&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&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;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&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="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// forgot i++&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where that code runs decides whether you lose a second or lose your editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: run it in the same thread
&lt;/h2&gt;

&lt;p&gt;The simplest scratchpad is &lt;code&gt;eval&lt;/code&gt; (or &lt;code&gt;new Function&lt;/code&gt;) in the page that hosts the editor. It's fast and it's one line. It's also a trap.&lt;/p&gt;

&lt;p&gt;JavaScript runs one thing at a time per thread. While that &lt;code&gt;while&lt;/code&gt; loop spins, nothing else on the thread runs: not your Stop button's click handler, not the editor's rendering, not the code that would show an error. Paste it into a browser console and that tab freezes until the browser offers to kill it.&lt;/p&gt;

&lt;p&gt;There's no &lt;code&gt;try/catch&lt;/code&gt; for "this never returns".&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: a worker
&lt;/h2&gt;

&lt;p&gt;A Web Worker (or a Node &lt;code&gt;worker_thread&lt;/code&gt;) runs on its own thread, so the UI stays responsive, and the host can stop it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./run.js&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="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;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&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;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better. But a worker still lives in your process. It shares the process's environment, its working directory and its fate: if something in it brings the process down, the editor goes too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: a separate process
&lt;/h2&gt;

&lt;p&gt;Give each run its own OS process and the operating system becomes the boundary. Here's a minimal runner that works in Node and in Bun:&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;execFile&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:child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;execFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&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;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timedOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;killed&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="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;// never-ends.js contains: while (true) {}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./never-ends.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { stdout: "", stderr: "", timedOut: true }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;timeout&lt;/code&gt; makes Node send the child a signal when time's up. What that buys you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kill always works.&lt;/strong&gt; A process can be killed from outside no matter what it's doing. No cooperation needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crashes stay contained.&lt;/strong&gt; A segfault in a native addon, an out-of-memory, a &lt;code&gt;process.exit()&lt;/code&gt; in a library: the child dies, the host carries on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every run starts clean.&lt;/strong&gt; No globals left over from the last run, no timers still firing from three edits ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real process semantics.&lt;/strong&gt; &lt;code&gt;process.env&lt;/code&gt;, &lt;code&gt;process.cwd()&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt;, exit codes and signals behave like they do in production, because it &lt;em&gt;is&lt;/em&gt; a process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cost is start-up time on every run. That's why the runtime you pick matters: one that starts fast and runs TypeScript without a separate compile step keeps the loop tight.&lt;/p&gt;

&lt;h2&gt;
  
  
  How JSLab does it
&lt;/h2&gt;

&lt;p&gt;JSLab, a JavaScript &amp;amp; TypeScript scratchpad for macOS, uses option 3. In a Bun tab, each run is a separate Bun process, using the Bun that ships inside the app. In a browser tab, each run is a freshly reloaded page. Either way, a crash or an infinite loop never takes the editor down.&lt;/p&gt;

&lt;p&gt;On top of the process boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⇧⌘R (Stop)&lt;/strong&gt; stops a run gracefully, and kills it after 500 ms if it doesn't respond. &lt;strong&gt;⌥⌘R (Kill)&lt;/strong&gt; kills it at once.&lt;/li&gt;
&lt;li&gt;If a run stops answering for 3 seconds, JSLab asks: &lt;strong&gt;Kill&lt;/strong&gt; or &lt;strong&gt;Wait&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop protection&lt;/strong&gt; stops a runaway &lt;code&gt;while&lt;/code&gt; loop like the one above after 2,000 iterations, with an error naming its line, before you even reach for Stop.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4o92rx8ejs08kg99qrur.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4o92rx8ejs08kg99qrur.png" alt="JSLab stopping a runaway loop: the code &amp;quot;let i = 0; while (i &lt; 10) { i = i * 2; }&amp;quot; above, and below it the error &amp;quot;RangeError: Potential infinite loop: exceeded 2000 iterations (line 3). Disable Loop Protection or raise the limit in Settings → Advanced.&amp;quot;, tagged to line 3." width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The details of Stop, Kill and loop protection are in today's feature note.&lt;/p&gt;

&lt;p&gt;JSLab launches Nov 15. How runs work: &lt;a href="https://js-lab.app/docs/editor" rel="noopener noreferrer"&gt;https://js-lab.app/docs/editor&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>bunjs</category>
      <category>node</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
