[View original](https://flaviocopes.com/javascript-single-executable)

<article class="blog-post prose max-w-none prose-newspaper" data-pagefind-body="true" data-astro-cid-62dzsm7q=""><nav class="not-prose text-xs mb-2" aria-label="Breadcrumb" style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q=""><a href="/" class="unset" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">Home</a><span data-astro-cid-62dzsm7q=""> / </span><a href="/tags/js/" class="unset" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">JavaScript</a></nav><header data-astro-cid-62dzsm7q=""><h1 class="font-bold text-xl uppercase" data-astro-cid-62dzsm7q="">How to compile JavaScript into a single executable</h1><p class="not-prose text-sm mb-1" data-astro-cid-62dzsm7q=""><span style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q="">By </span><a href="/about/" rel="author" class="unset" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">Flavio Copes</a></p><time datetime="2026-10-07T10:00:00.000Z" class="block mb-6 text-sm" style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q="">Oct 7, 2026</time><p class="mb-6 text-sm" style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q="">Turn a JavaScript program into one executable file, like Go and Rust do, with deno compile, bun build --compile and Node.js single executable apps.</p></header><div class="separator" data-astro-cid-62dzsm7q="">~~~</div><p>Yes, you can turn a JavaScript program into one executable file, the way you do with Go or Rust. Deno has <code>deno compile</code>, Bun has <code>bun build --compile</code>, and Node.js builds what it calls a single executable application with <code>node --build-sea</code>.</p>

<p>You send someone that file, they run it, and it works. They don’t need Node.js, Deno or Bun installed, and there’s no <code>node\_modules</code> folder to copy around.</p>

<p>In this post we’ll take a small command-line tool and compile it with all three. Same source file, three executables, from 62 MB to 146 MB.</p>

<h2 id="how-is-this-different-from-go-or-rust">How is this different from Go or Rust?</h2>

<p>When you run <code>cargo build</code>, the Rust compiler turns your code into machine code. The tool we’re about to build is a 446 KB file when written in Rust.</p>

<p>The JavaScript tools don’t produce machine code. They take a copy of the runtime, the same program you run when you type <code>node</code> or <code>bun</code>, and put your JavaScript inside it. When you start the file, the runtime finds the embedded code and runs it like any other script.</p>

<p>So “compile” is a bit of a stretch here, and it’s why the files are big. A 23-line program becomes a 60 to 150 MB executable, because the whole JavaScript engine travels with it.</p>

<p>It’s still very useful. You can give a CLI to people who have never heard of npm, or copy one file to a server instead of installing a runtime and running <code>npm install</code> there.</p>

<h2 id="the-program-well-compile">The program we’ll compile</h2>

<p>We’ll build <code>readtime</code>, a small tool that counts the words in a Markdown file and tells you how long it takes to read.</p>

<p>It uses two Node.js built-in modules and one npm package, <code>picocolors</code>, to color the output. That npm package is the interesting part, because each tool handles dependencies in its own way.</p>

<p>Create a folder and install the package:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">mkdir</span><span style="color:#9ECBFF"> readtime</span></span>

<span class="line"><span style="color:#79B8FF">cd</span><span style="color:#9ECBFF"> readtime</span></span>

<span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> init</span><span style="color:#79B8FF"> -y</span></span>

<span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> pkg</span><span style="color:#9ECBFF"> set</span><span style="color:#9ECBFF"> type=module</span></span>

<span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> picocolors</span></span></code></pre>

<p><code>npm pkg set type=module</code> tells Node.js we write ES modules, so we can use <code>import</code>.</p>

<p>Now create <code>readtime.js</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { readFileSync } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:fs'</span></span>

<span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { parseArgs } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:util'</span></span>

<span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> pc </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'picocolors'</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">values</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">positionals</span><span style="color:#E1E4E8"> } </span><span style="color:#F97583">=</span><span style="color:#B392F0"> parseArgs</span><span style="color:#E1E4E8">({</span></span>

<span class="line"><span style="color:#E1E4E8"> allowPositionals: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#E1E4E8"> options: {</span></span>

<span class="line"><span style="color:#E1E4E8"> wpm: { type: </span><span style="color:#9ECBFF">'string'</span><span style="color:#E1E4E8">, default: </span><span style="color:#9ECBFF">'200'</span><span style="color:#E1E4E8"> },</span></span>

<span class="line"><span style="color:#E1E4E8"> },</span></span>

<span class="line"><span style="color:#E1E4E8">})</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> file</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> positionals\[</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">\]</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> (</span><span style="color:#F97583">!</span><span style="color:#E1E4E8">file) {</span></span>

<span class="line"><span style="color:#E1E4E8"> console.</span><span style="color:#B392F0">error</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'Usage: readtime &lt;[file.md](http://file.md)&gt; \[--wpm 200\]'</span><span style="color:#E1E4E8">)</span></span>

<span class="line"><span style="color:#E1E4E8"> process.</span><span style="color:#B392F0">exit</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">)</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> text</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> readFileSync</span><span style="color:#E1E4E8">(file, </span><span style="color:#9ECBFF">'utf8'</span><span style="color:#E1E4E8">)</span></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> words</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> text.</span><span style="color:#B392F0">split</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">/</span><span style="color:#79B8FF">\\s</span><span style="color:#F97583">+</span><span style="color:#9ECBFF">/</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">filter</span><span style="color:#E1E4E8">(Boolean).</span><span style="color:#79B8FF">length</span></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> minutes</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> Math.</span><span style="color:#B392F0">ceil</span><span style="color:#E1E4E8">(words </span><span style="color:#F97583">/</span><span style="color:#B392F0"> Number</span><span style="color:#E1E4E8">(values.wpm))</span></span>

<span class="line"></span>

<span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`${</span><span style="color:#E1E4E8">pc</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">bold</span><span style="color:#9ECBFF">(</span><span style="color:#E1E4E8">file</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">}: ${</span><span style="color:#E1E4E8">words</span><span style="color:#9ECBFF">} words, ${</span><span style="color:#E1E4E8">pc</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">green</span><span style="color:#9ECBFF">(</span><span style="color:#9ECBFF">`${</span><span style="color:#E1E4E8">minutes</span><span style="color:#9ECBFF">} min read`</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">}`</span><span style="color:#E1E4E8">)</span></span></code></pre>

<p><code>parseArgs</code> from <code>node:util</code> reads the command line arguments, so we don’t need another dependency for that. I explain it in <a href="/node-cli-args/">how to accept arguments from the command line in Node</a>. If you want to build a bigger CLI first, the free <a href="/courses/nodejs/build-a-notes-cli/">Node.js course</a> builds a notes tool step by step.</p>

<p>Let’s try it on the Markdown source of my Deno post:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">node</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>[deno.md](http://deno.md): 1002 words, 6 min read</span></span></code></pre>

<p>Pass <code>--wpm</code> to change the reading speed:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">node</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span><span style="color:#79B8FF"> --wpm</span><span style="color:#79B8FF"> 250</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>[deno.md](http://deno.md): 1002 words, 5 min read</span></span></code></pre>

<p>The same file runs on Bun and Deno without changes, since both support Node’s built-in modules:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">bun</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span>

<span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> run</span><span style="color:#79B8FF"> --allow-read</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<p>Deno needs <code>--allow-read</code> because it doesn’t let a program read files unless you say so. Keep that in mind, it comes back when we compile.</p>

<h2 id="compile-with-deno">Compile with Deno</h2>

<p>The command is <code>deno compile</code>. If you haven’t used Deno before, my <a href="/deno/">introduction to Deno</a> covers the basics.</p>

<p>Let’s try it:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> compile</span><span style="color:#79B8FF"> --allow-read</span><span style="color:#79B8FF"> --output</span><span style="color:#9ECBFF"> readtime</span><span style="color:#9ECBFF"> readtime.js</span></span></code></pre>

<p>In a project with a <code>package.json</code> and a <code>node\_modules</code> folder, this first try fails:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>error: Error: Could not find a matching package for 'npm:@types/node' in the node\_modules directory.</span></span></code></pre>

<p><code>deno compile</code> type-checks your code before it builds anything. In a project managed by npm, Deno looks for the Node.js type definitions in <code>node\_modules</code>, and they aren’t there.</p>

<p>Our file is plain JavaScript, so there’s nothing to check. Skip the check with <code>--no-check</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> compile</span><span style="color:#79B8FF"> --no-check</span><span style="color:#79B8FF"> --allow-read</span><span style="color:#79B8FF"> --output</span><span style="color:#9ECBFF"> readtime</span><span style="color:#9ECBFF"> readtime.js</span></span></code></pre>

<p>If you write TypeScript and want the check, install the types with <code>npm install -D @types/node</code> instead. Be aware that Deno then embeds your whole <code>node\_modules</code> folder in the executable, dev dependencies included. For this program that meant 2.5 MB of type definitions sitting next to 600 bytes of code.</p>

<p>Now run the file we just built:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">./readtime</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>[deno.md](http://deno.md): 1002 words, 6 min read</span></span></code></pre>

<h3 id="permissions-are-baked-in">Permissions are baked in</h3>

<p>Notice that we passed <code>--allow-read</code> to <code>deno compile</code>, not when running the file. The permissions you give at compile time are stored in the executable, and whoever runs it can’t add more.</p>

<p>If you forget them, the program fails when it tries to read the file:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>error: Uncaught (in promise) NotCapable: Requires read access to "[deno.md](http://deno.md)", specify the required permissions during compilation using `deno compile --allow-read`</span></span></code></pre>

<p>So you decide once what your tool is allowed to do, and you can list it in the README.</p>

<h3 id="build-for-other-platforms">Build for other platforms</h3>

<p>An executable only runs on the operating system and CPU it was built for. Mine runs on a Mac with Apple Silicon. To build for another platform, from the same machine, add <code>--target</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> compile</span><span style="color:#79B8FF"> --no-check</span><span style="color:#79B8FF"> --allow-read</span><span style="color:#79B8FF"> --target</span><span style="color:#9ECBFF"> x86\_64-unknown-linux-gnu</span><span style="color:#79B8FF"> --output</span><span style="color:#9ECBFF"> readtime-linux</span><span style="color:#9ECBFF"> readtime.js</span></span>

<span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> compile</span><span style="color:#79B8FF"> --no-check</span><span style="color:#79B8FF"> --allow-read</span><span style="color:#79B8FF"> --target</span><span style="color:#9ECBFF"> x86\_64-pc-windows-msvc</span><span style="color:#79B8FF"> --output</span><span style="color:#9ECBFF"> readtime</span><span style="color:#9ECBFF"> readtime.js</span></span></code></pre>

<p>The second command produces <code>readtime.exe</code>. These are the targets Deno supports:</p>

<ul>

<li><code>x86\_64-unknown-linux-gnu</code> and <code>aarch64-unknown-linux-gnu</code> for Linux</li>

<li><code>x86\_64-apple-darwin</code> and <code>aarch64-apple-darwin</code> for macOS</li>

<li><code>x86\_64-pc-windows-msvc</code> and <code>aarch64-pc-windows-msvc</code> for Windows</li>

</ul>

<p>The first time you build for a target, Deno downloads a slimmed-down runtime for that platform, called <code>denort</code>, and caches it for the next builds.</p>

<h3 id="embed-files">Embed files</h3>

<p>Deno embeds the code it finds by following your imports. If your program reads a file at runtime, add it with <code>--include</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">deno</span><span style="color:#9ECBFF"> compile</span><span style="color:#79B8FF"> --no-check</span><span style="color:#79B8FF"> --include</span><span style="color:#9ECBFF"> help.txt</span><span style="color:#79B8FF"> --output</span><span style="color:#9ECBFF"> readtime</span><span style="color:#9ECBFF"> readtime.js</span></span></code></pre>

<p>Then read it relative to <code>import.meta.dirname</code>, which points inside the executable:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { readFileSync } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:fs'</span></span>

<span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { join } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:path'</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> help</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> readFileSync</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">join</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">import</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">meta</span><span style="color:#E1E4E8">.dirname, </span><span style="color:#9ECBFF">'help.txt'</span><span style="color:#E1E4E8">), </span><span style="color:#9ECBFF">'utf8'</span><span style="color:#E1E4E8">)</span></span></code></pre>

<p>Reading an embedded file doesn’t need <code>--allow-read</code>. Without <code>--include</code>, the program fails with a <code>NotFound: path not found</code> error the first time it reaches that line.</p>

<p>Two more flags are worth knowing, and both are experimental. <code>--bundle</code> runs your code through Deno’s bundler first, so only the code you use ends up in the executable instead of your whole <code>node\_modules</code> folder. <code>--engine quickjs</code> swaps V8 for QuickJS, a much smaller JavaScript engine, and the file drops from 68 MB to 37 MB. For <code>readtime</code> it also started slower (75 ms instead of 20 ms), so measure your own program before switching.</p>

<p>All the options are in the <a href="[https://docs.deno.com/runtime/reference/cli/compile/"><code>deno](https://docs.deno.com/runtime/reference/cli/compile/"\>\<code\>deno) compile</code> docs</a>.</p>

<aside class="post-banner post-banner-mid not-prose" aria-label="Solo Lab: make a living from your own software" data-pagefind-ignore=""><p class="post-banner-kicker">Starts 28 October · waiting list open</p><p class="post-banner-title">Solo Lab: make a living from your own software</p><p class="post-banner-text">A three-week intensive on finding customers, pricing and selling your work, and running a calm business on your own. It starts on 28 October. Join the waiting list and I'll email you when enrollment opens.</p><a href="/courses/solo-lab/" class="post-banner-cta unset" data-event="Solo Lab banner click" data-event-prop-placement="blog-post-mid">Join the Solo Lab waiting list →</a></aside><h2 id="compile-with-bun">Compile with Bun</h2>

<p>In Bun, compiling is a flag of the bundler:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">bun</span><span style="color:#9ECBFF"> build</span><span style="color:#79B8FF"> --compile</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#79B8FF"> --outfile</span><span style="color:#9ECBFF"> readtime</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span> \[3ms\] bundle 2 modules</span></span>

<span class="line"><span> \[81ms\] compile readtime</span></span></code></pre>

<p>That’s it. Bun followed the import, bundled <code>picocolors</code> with our code, and put the result inside a copy of the Bun runtime:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">./readtime</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>[deno.md](http://deno.md): 1002 words, 6 min read</span></span></code></pre>

<p>There’s no type check and no permission system, so there’s nothing else to set up.</p>

<h3 id="build-for-other-platforms-1">Build for other platforms</h3>

<p>Same idea as Deno, with Bun’s own target names:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">bun</span><span style="color:#9ECBFF"> build</span><span style="color:#79B8FF"> --compile</span><span style="color:#79B8FF"> --target=bun-linux-x64</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#79B8FF"> --outfile</span><span style="color:#9ECBFF"> readtime-linux</span></span>

<span class="line"><span style="color:#B392F0">bun</span><span style="color:#9ECBFF"> build</span><span style="color:#79B8FF"> --compile</span><span style="color:#79B8FF"> --target=bun-windows-x64</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#79B8FF"> --outfile</span><span style="color:#9ECBFF"> readtime</span></span></code></pre>

<p>The targets are <code>bun-linux-x64</code>, <code>bun-linux-arm64</code>, <code>bun-darwin-x64</code>, <code>bun-darwin-arm64</code>, <code>bun-windows-x64</code> and <code>bun-windows-arm64</code>.</p>

<p>Bun also has <code>bun-linux-x64-musl</code> and <code>bun-linux-arm64-musl</code> for Alpine Linux, which you’ll find in a lot of Docker images. Deno has no musl target, and Node’s single executable applications aren’t tested on Alpine.</p>

<p>Like Deno, Bun downloads the runtime for each target once and caches it.</p>

<h3 id="production-flags">Production flags</h3>

<p>For something you ship to other people, Bun recommends a few more flags:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">bun</span><span style="color:#9ECBFF"> build</span><span style="color:#79B8FF"> --compile</span><span style="color:#79B8FF"> --minify</span><span style="color:#79B8FF"> --sourcemap</span><span style="color:#79B8FF"> --bytecode</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#79B8FF"> --outfile</span><span style="color:#9ECBFF"> readtime</span></span></code></pre>

<p><code>--minify</code> makes the code smaller. <code>--sourcemap</code> embeds a source map, so stack traces point at your original lines. <code>--bytecode</code> does the parsing at build time, so the program starts faster. For <code>readtime</code> startup went from 6.8 ms to 5.7 ms, which nobody notices on a tool this small, but it adds up on a big CLI.</p>

<h3 id="embed-files-1">Embed files</h3>

<p>Bun embeds the files you import. A <code>.txt</code> file comes in as a string:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> help </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> './help.txt'</span></span>

<span class="line"></span>

<span class="line"><span style="color:#E1E4E8">console.</span><span style="color:#B392F0">log</span><span style="color:#E1E4E8">(help)</span></span></code></pre>

<p>For any other file, add <code>with { type: 'file' }</code>. You get back a path that works with <code>Bun.file()</code> and with <code>node:fs</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { readFileSync } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:fs'</span></span>

<span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> logo </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> './logo.png'</span><span style="color:#F97583"> with</span><span style="color:#E1E4E8"> { type: </span><span style="color:#9ECBFF">'file'</span><span style="color:#E1E4E8"> }</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> bytes</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> readFileSync</span><span style="color:#E1E4E8">(logo)</span></span></code></pre>

<p>Bun can also compile a full web app into one file: import an HTML file in your server code and it bundles the frontend into the same executable. My <a href="/bun/">Bun guide</a> shows how to write the server, and the free <a href="/courses/bun/compile-and-ship-the-application/">Bun course</a> ends by compiling and shipping a small API.</p>

<p>The full list of options is in <a href="[https://bun.com/docs/bundler/executables">Bun’s](https://bun.com/docs/bundler/executables"\>Bun’s) executables docs</a>.</p>

<h2 id="compile-with-nodejs">Compile with Node.js</h2>

<p>Node.js calls this feature single executable applications, or SEA. It takes more steps than Deno and Bun, for one reason: Node doesn’t bundle your code, so you have to do that first.</p>

<p>Let’s see what happens if we skip that. Create a <code>sea-config.json</code> file:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>

<span class="line"><span style="color:#79B8FF"> "main"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"readtime.js"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "mainFormat"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"module"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "output"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"readtime"</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p><code>mainFormat</code> tells Node our file is an ES module. Now build it:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">node</span><span style="color:#79B8FF"> --build-sea</span><span style="color:#9ECBFF"> sea-config.json</span></span></code></pre>

<p><code>--build-sea</code> needs Node.js 25.5 or later. I’ll show the older way for Node 24 and 22 at the end of this section.</p>

<p>On a Mac there’s one more step. Node writes your code into a copy of its own binary, and that breaks the binary’s signature. macOS on Apple Silicon kills a program with a broken signature, so if you run it now it exits right away with code 137. Sign it again with an ad-hoc signature:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">codesign</span><span style="color:#79B8FF"> --sign</span><span style="color:#9ECBFF"> -</span><span style="color:#9ECBFF"> readtime</span></span></code></pre>

<p>Deno and Bun do this for you, which is why we didn’t need it before. On Linux there’s nothing to sign, and on Windows signing is optional.</p>

<p>Now run it:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">./readtime</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>Error \[ERR\_UNKNOWN\_BUILTIN\_MODULE\]: No such built-in module: picocolors</span></span></code></pre>

<p>Inside a single executable application, <code>import</code> and <code>require()</code> can only load Node’s built-in modules. Our code imports <code>picocolors</code> from <code>node\_modules</code>, and that folder isn’t inside the executable.</p>

<p>The fix is to bundle everything into one JavaScript file first. esbuild does it in one command:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> install</span><span style="color:#79B8FF"> -D</span><span style="color:#9ECBFF"> esbuild</span></span>

<span class="line"><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> esbuild</span><span style="color:#9ECBFF"> readtime.js</span><span style="color:#79B8FF"> --bundle</span><span style="color:#79B8FF"> --platform=node</span><span style="color:#79B8FF"> --outfile=dist/readtime.cjs</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span> dist/readtime.cjs 5.2kb</span></span></code></pre>

<p>With <code>--platform=node</code>, esbuild leaves the <code>node:</code> imports alone and outputs CommonJS, the format Node expects for the embedded script by default. Any bundler works here, esbuild is the quickest to set up.</p>

<p>Point <code>sea-config.json</code> at the bundle:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>

<span class="line"><span style="color:#79B8FF"> "main"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"dist/readtime.cjs"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "output"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"readtime"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "disableExperimentalSEAWarning"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p><code>disableExperimentalSEAWarning</code> hides the warning Node otherwise prints on every run, saying that single executable applications are an experimental feature. Your users don’t need to see that.</p>

<p>Build, sign and run:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">node</span><span style="color:#79B8FF"> --build-sea</span><span style="color:#9ECBFF"> sea-config.json</span></span>

<span class="line"><span style="color:#B392F0">codesign</span><span style="color:#79B8FF"> --sign</span><span style="color:#9ECBFF"> -</span><span style="color:#9ECBFF"> readtime</span></span>

<span class="line"><span style="color:#B392F0">./readtime</span><span style="color:#9ECBFF"> [deno.md](http://deno.md)</span></span></code></pre>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>[deno.md](http://deno.md): 1002 words, 6 min read</span></span></code></pre>

<p>That’s three commands to remember, so put them in a <code>build</code> script in <code>package.json</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#9ECBFF">"scripts"</span><span style="color:#E1E4E8">: {</span></span>

<span class="line"><span style="color:#79B8FF"> "build"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"esbuild readtime.js --bundle --platform=node --outfile=dist/readtime.cjs &amp;&amp; node --build-sea sea-config.json &amp;&amp; codesign --sign - readtime"</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p>Now <code>npm run build</code> does everything. On Linux, drop the <code>codesign</code> part.</p>

<h3 id="embed-files-2">Embed files</h3>

<p>Node has its own API for embedded files. List them under <code>assets</code> in <code>sea-config.json</code>:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>

<span class="line"><span style="color:#79B8FF"> "main"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"dist/readtime.cjs"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "output"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"readtime"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "disableExperimentalSEAWarning"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "assets"</span><span style="color:#E1E4E8">: {</span></span>

<span class="line"><span style="color:#79B8FF"> "help.txt"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"help.txt"</span></span>

<span class="line"><span style="color:#E1E4E8"> }</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p>Then read them with <code>getAsset()</code> from the <code>node:sea</code> module:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { getAsset, isSea } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> 'node:sea'</span></span>

<span class="line"></span>

<span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> help</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> isSea</span><span style="color:#E1E4E8">() </span><span style="color:#F97583">?</span><span style="color:#B392F0"> getAsset</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'help.txt'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">'utf8'</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> 'Run the compiled readtime to see the help'</span></span></code></pre>

<p><code>getAsset()</code> throws when your code isn’t running inside an executable, for example when you run <code>node readtime.js</code> while developing, so check <code>isSea()</code> first.</p>

<p>Recent Node versions are also adding a <code>useVfs</code> option that lets you read embedded files with the regular <code>node:fs</code> functions, but it’s still in early development.</p>

<h3 id="build-for-other-platforms-2">Build for other platforms</h3>

<p>Node has no <code>--target</code> flag. Instead, you download the Node.js build for the platform you want from <a href="[https://nodejs.org/en/download">nodejs.org</a>](https://nodejs.org/en/download"\>nodejs.org\</a\>), take its <code>node</code> binary, and point the <code>executable</code> field at it. Here I copied the <code>node</code> binary from the Linux x64 download into a <code>linux</code> folder:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>

<span class="line"><span style="color:#79B8FF"> "main"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"dist/readtime.cjs"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "output"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"readtime-linux"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "executable"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"linux/node"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "disableExperimentalSEAWarning"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p>That binary must be the same Node version as the one running <code>--build-sea</code>. Leave <code>useCodeCache</code> and <code>useSnapshot</code> off in this case too, because the cache they create only works on the platform that created it.</p>

<h3 id="on-nodejs-24-and-older">On Node.js 24 and older</h3>

<p>Before Node.js 25.5 there’s no <code>--build-sea</code>, and you get <code>node: bad option: --build-sea</code>. The older way has more steps. Node writes a preparation blob, and a separate tool called postject injects it into a copy of the <code>node</code> binary.</p>

<p>Set <code>output</code> in <code>sea-config.json</code> to the blob file:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#E1E4E8">{</span></span>

<span class="line"><span style="color:#79B8FF"> "main"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"dist/readtime.cjs"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "output"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"sea-prep.blob"</span><span style="color:#E1E4E8">,</span></span>

<span class="line"><span style="color:#79B8FF"> "disableExperimentalSEAWarning"</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span></span>

<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>

<p>Then run these commands on a Mac:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">node</span><span style="color:#79B8FF"> --experimental-sea-config</span><span style="color:#9ECBFF"> sea-config.json</span></span>

<span class="line"><span style="color:#B392F0">cp</span><span style="color:#E1E4E8"> $(</span><span style="color:#79B8FF">command</span><span style="color:#79B8FF"> -v</span><span style="color:#9ECBFF"> node</span><span style="color:#E1E4E8">) </span><span style="color:#9ECBFF">readtime</span></span>

<span class="line"><span style="color:#B392F0">codesign</span><span style="color:#79B8FF"> --remove-signature</span><span style="color:#9ECBFF"> readtime</span></span>

<span class="line"><span style="color:#B392F0">npx</span><span style="color:#9ECBFF"> postject</span><span style="color:#9ECBFF"> readtime</span><span style="color:#9ECBFF"> NODE\_SEA\_BLOB</span><span style="color:#9ECBFF"> sea-prep.blob</span><span style="color:#79B8FF"> \\</span></span>

<span class="line"><span style="color:#79B8FF"> --sentinel-fuse</span><span style="color:#9ECBFF"> NODE\_SEA\_FUSE\_fce680ab2cc467b6e072b8b5df1996b2</span><span style="color:#79B8FF"> \\</span></span>

<span class="line"><span style="color:#79B8FF"> --macho-segment-name</span><span style="color:#9ECBFF"> NODE\_SEA</span></span>

<span class="line"><span style="color:#B392F0">codesign</span><span style="color:#79B8FF"> --sign</span><span style="color:#9ECBFF"> -</span><span style="color:#9ECBFF"> readtime</span></span></code></pre>

<p>On Linux, skip both <code>codesign</code> commands and the <code>--macho-segment-name</code> option. These older versions only accept a CommonJS entry point, which is fine because esbuild already gave us one.</p>

<p>Everything else is in the <a href="[https://nodejs.org/api/single-executable-applications.html">Node.js](https://nodejs.org/api/single-executable-applications.html"\>Node.js) single executable applications docs</a>.</p>

<h2 id="size-and-speed-compared">Size and speed compared</h2>

<p>Here’s what the three tools produced for <code>readtime</code>, next to the same tool written in Rust. The numbers are from October 2026, on an Apple Silicon Mac with the current release of each tool. Startup is the median of 40 runs of <code>readtime [deno.md](http://deno.md)</code>.</p>

<table><thead><tr><th></th><th>File size</th><th>Startup</th></tr></thead><tbody><tr><td>Rust</td><td>446 KB</td><td>1.3 ms</td></tr><tr><td>Bun</td><td>62 MB</td><td>7 ms</td></tr><tr><td>Deno</td><td>68 MB</td><td>20 ms</td></tr><tr><td>Node.js</td><td>146 MB</td><td>24 ms</td></tr></tbody></table>

<p>The Linux x64 builds came out bigger than the macOS ones: 81 MB for Bun, 105 MB for Deno and 150 MB for Node.</p>

<p>Bun made the smallest file of the three and started the fastest. Most of that comes from the runtime: the Bun binary is smaller than the Node binary to begin with.</p>

<p>Node made the biggest file, because the <code>node</code> binary is about 147 MB before your code goes in. Running <code>node readtime.js</code> directly also took 24 ms, so the executable starts as fast as plain Node, no faster. Turning on <code>useCodeCache</code> made no difference for a program this small.</p>

<p>Rust is in another league, with a file more than 100 times smaller that starts in about a millisecond. If size or startup time really matter for your tool, JavaScript is the wrong language for it, and at that point I’d write it in Rust.</p>

<h2 id="things-to-know-before-you-ship">Things to know before you ship</h2>

<h3 id="one-file-per-platform">One file per platform</h3>

<p>A macOS executable doesn’t run on Linux, and an x64 Linux executable doesn’t run on an ARM server. If you publish a CLI, build one file for each platform you support and let people download the right one.</p>

<h3 id="your-code-isnt-hidden">Your code isn’t hidden</h3>

<p>Compiling doesn’t hide or encrypt your JavaScript. It sits inside the executable as text, and anyone can read it:</p>

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">strings</span><span style="color:#9ECBFF"> readtime</span><span style="color:#F97583"> |</span><span style="color:#B392F0"> grep</span><span style="color:#9ECBFF"> "min read"</span></span></code></pre>

<p>With all three executables this prints our <code>console.log()</code> line, and Bun’s <code>--bytecode</code> build doesn’t hide the source either. Never put API keys or other secrets in the code. Read them from environment variables when the program runs.</p>

<h3 id="signing-on-macos-and-windows">Signing on macOS and Windows</h3>

<p>The ad-hoc signature from <code>codesign --sign -</code> is enough to run the file on your own Mac. If people download it with a browser, macOS blocks it until they approve it by hand. To avoid that, sign it with an Apple Developer ID certificate and notarize it, the same two steps I describe for Mac apps in <a href="/macos-open-anyway/">how to avoid the “Open Anyway” message</a>. Files downloaded with <code>curl</code> skip that check, which is one reason so many CLIs install with a <code>curl</code> command.</p>

<p>On Windows, SmartScreen warns people before they run an unsigned <code>.exe</code> they downloaded.</p>

<h3 id="code-you-load-dynamically">Code you load dynamically</h3>

<p>All three tools find your code by following <code>import</code> statements. A dynamic <code>import()</code> with a computed path, a worker file or a file you read at runtime is invisible to them, so you add it yourself: <code>--include</code> in Deno, an extra entry point or a file import in Bun, <code>assets</code> in Node.</p>

<p>Native addons, the <code>.node</code> files some npm packages ship, are the hardest case. Bun can embed them. Deno has a <code>--self-extracting</code> mode that writes the embedded files to disk on the first run. With Node, you write the addon to a temporary file yourself and load it from there.</p>

<h3 id="bun-and-deno-use-their-own-runtime">Bun and Deno use their own runtime</h3>

<p>Our <code>readtime.js</code> is plain Node.js code, and Bun and Deno compiled it without complaining. You don’t have to move your project to Bun or Deno to use them as a compiler.</p>

<p>But the executable runs on Bun or Deno, not on Node. Test the compiled file before you ship it, especially if you use less common Node APIs.</p>

<h2 id="what-about-pkg-and-nexe">What about pkg and nexe?</h2>

<p>Before Node had single executable applications, most people used Vercel’s <code>pkg</code>. It’s archived now. The last release was 5.8.1, and its README points to Node’s single executable applications. If an old project depends on it, the community fork <code>@yao-pkg/pkg</code> is still maintained. <code>nexe</code> is the other tool you’ll find in old tutorials.</p>

<p>For a new project, use one of the three built-in tools.</p>

<h2 id="which-one-should-you-use">Which one should you use?</h2>

<p>My advice is to start with Bun. It’s one command, it bundles your npm packages, it builds for Linux (Alpine included), macOS and Windows from one machine, and it gave us the smallest and fastest executable of the three.</p>

<p>Use <code>deno compile</code> if your project already runs on Deno, or if you like the idea of locking permissions into the binary.</p>

<p>Use Node’s single executable applications when your program needs Node itself, because of native modules, Node-specific behavior or a team that doesn’t want a second runtime. It takes a bundler and a few more commands, and <code>--build-sea</code> made it a lot simpler than it used to be.</p><div class="not-prose mt-8 text-sm" data-astro-cid-62dzsm7q=""><span style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q="">Tagged: </span><a href="/tags/js/" class="unset" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">JavaScript</a><span style="color: rgb(var(--color-text-muted))" data-astro-cid-62dzsm7q=""> · </span><a href="/blog/#topics" class="unset" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">All topics</a></div><div class="not-prose mt-8" data-astro-cid-62dzsm7q=""><iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" allowfullscreen="true" class="twitter-follow-button twitter-follow-button-rendered" style="position: static; visibility: visible; width: 155px; height: 20px;" title="Twitter Follow Button" src="[https://platform.twitter.com/widgets/follow\_button.1227a5674072e080ffb1ba14ac0c1079.en.html#dnt=false&amp;id=twitter-widget-0&amp;lang=en&amp;screen\_name=flaviocopes&amp;show\_count=false&amp;show\_screen\_name=true&amp;size=m&amp;time=1791794429159](https://platform.twitter.com/widgets/follow_button.1227a5674072e080ffb1ba14ac0c1079.en.html#dnt=false&amp;id=twitter-widget-0&amp;lang=en&amp;screen_name=flaviocopes&amp;show_count=false&amp;show_screen_name=true&amp;size=m&amp;time=1791794429159)" data-screen-name="flaviocopes"></iframe></div><p class="not-prose mt-8 text-sm" data-pagefind-ignore="" data-astro-cid-62dzsm7q="">Want me to talk about your product? You can <a href="/sponsor/" class="unset font-bold" style="color: rgb(var(--color-text-link))" data-astro-cid-62dzsm7q="">sponsor this site</a>.</p><aside class="not-prose my-10 p-5 text-sm" style="background-color: rgb(var(--color-bg-code)); border: 1px solid rgb(var(--color-border-code));"><p class="font-bold uppercase tracking-wide mb-2 text-xs" style="color: rgb(var(--color-text-muted))">Free ebook</p><p class="mb-4 leading-relaxed">Enjoyed this? Grab my <strong>JavaScript Handbook</strong> for free. Subscribe to my newsletter and download it, together with all my other programming books, courses, and software.</p><a href="/ebooks/js-handbook/" class="unset inline-block border border-current px-4 py-2 font-bold hover:opacity-70" style="color: rgb(var(--color-text-link))">Download the JavaScript Handbook →</a><p class="mt-5 pt-4 leading-relaxed" style="border-top: 1px solid rgb(var(--color-border-code));">Want to go further? Join my <a href="/courses/typescript/" class="unset font-bold" style="color: rgb(var(--color-text-link))">Free TypeScript Course</a>.</p></aside><div data-pagefind-ignore=""><div class="separator">~~~</div><p class="text-sm font-bold uppercase mb-3">Related posts about js:</p><div><ul><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/x-prevent-links/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">How to stop X from turning a domain into a link</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/browser-apis-replacing-libraries/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">10 browser APIs that replaced a library I used to install</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/what-happens-when-bun-runs-typescript/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">What happens when Bun runs a TypeScript file</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/javascript-object-groupby/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">How to group array items with Object.groupBy()</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/javascript-immutable-array-methods/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">JavaScript immutable array methods: toSorted(), toReversed(), toSpliced(), with()</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/abortcontroller/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">AbortController: how to cancel a fetch request in JavaScript</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/webassembly/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">WebAssembly tutorial</a></div></li><li class="mb-1" data-astro-cid-a6sclapi=""><div class="flex flex-wrap items-baseline gap-x-2 text-sm" data-astro-cid-a6sclapi=""><a href="/how-hono-is-built/" class="unset underline hover:opacity-70" style="color: rgb(var(--color-text-link))" data-astro-cid-a6sclapi="">A deep dive into Hono</a></div></li></ul></div></div></article>