Skip to content
HN On Hacker News ↗

Deno 2.9 | Deno

▲ 145 points 61 comments by enz 2w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is mainly AI-generated, with some AI-assisted and human-written content

78 %

AI likelihood · overall

AI
15% human-written 81% AI-generated
SEGMENTS · HUMAN 0 of 6
SEGMENTS · AI 6 of 6
WORD COUNT 1,670
PEAK AI % 99% · §1
Analyzed
Jun 25
backend: pangram/v3.3
Segments scanned
6 windows
avg 278 words each
Distribution
15 / 81%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,670 words · 6 segments analyzed

Human AI-generated
§1 AI · 99%

Deno 2.9 is here, headlined by deno desktop, a new way to build native desktop applications from the web stack you already know, with no Electron boilerplate and a single binary at the end. It’s also the easiest release yet to bring an existing Node project over: deno install now reads npm, pnpm, yarn, and Bun lockfiles directly, so switching your package manager to Deno takes a couple of commands, not a migration. There’s plenty more below, from CSS module imports and a much stronger test runner to faster startup and Node.js 26 compatibility. To upgrade to Deno 2.9, run the following in your terminal: deno upgradeIf Deno is not yet installed, run one of the following commands to install or learn how to install it here. curl -fsSL https://deno.land/install.sh | sh iwr https://deno.land/install.ps1 -useb | iexdeno desktop Building a desktop app has usually meant pulling in Electron or Tauri, wiring up a separate toolchain, and shipping a bundle that bears little resemblance to the rest of your project. Deno 2.9 introduces deno desktop. Point it at a script (or a web framework project) and it produces a native, self-contained desktop application where the UI runs in a webview, your logic runs in Deno, and the whole thing compiles down to a single distributable binary (#33441). deno desktop is experimental in 2.9. The surface described here is stabilizing and some platform features are still landing. The simplest app is an entrypoint that serves your UI. Deno.serve() inside a desktop entrypoint automatically binds to the port the webview opens, so there’s no port wiring to do: main.tsDeno.serve(() => new Response( "<!DOCTYPE html><h1>Hello from Deno desktop 👋</h1>", { headers: { "content-type": "text/html" } }, ) );$ deno desktop main.tsThat opens a native window rendering your page. deno desktop shares the same framework detection as deno compile: run it with no entrypoint (or deno desktop .)

§2 AI · 99%

and it auto-detects the web framework in the current directory (Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, and Vite SSR are all supported), builds it, and wraps the result: $ deno desktop $ deno desktop --hmr Native desktop APIs Richer apps get a full set of native desktop APIs built right into the runtime under Deno.*, available immediately with no extra dependencies. Deno.BrowserWindow gives you programmatic control over window size, position, visibility, menus, and DevTools, and lets you bridge between the webview and Deno: bind a function in the entrypoint with window.bind() and call it from page JavaScript via the bindings namespace. There’s also Deno.Tray for system-tray icons and panels, and Deno.Dock on macOS: tray.tsconst tray = new Deno.Tray(); tray.setIcon(iconBytes); const panel = tray.attachPanel({ url: "https://localhost:8000/panel" }); panel.window.bind("doThing", async () => {});prompt(), alert(), and confirm() render as native dialogs, and Deno.autoUpdate() wires up a polling auto-updater that applies binary patches in the background. Webview or CEF Every desktop app needs a browser engine to draw its UI, and deno desktop ships two, selected with --backend: webview (the default) renders with the operating system’s built-in engine: WebView2 on Windows, WebKit on macOS and Linux. Nothing extra is bundled, so binaries stay small and launch fast. The tradeoff is that rendering follows whatever engine the host ships. cef bundles Chromium through the Chromium Embedded Framework, so every user gets the same modern engine on every platform. That adds tens of megabytes and a download at build time, but guarantees identical rendering and the latest web-platform features everywhere. $ deno desktop main.ts $ deno desktop --backend cef main.ts Most apps are happiest on the default webview; reach for cef when you need a guaranteed-identical engine on every platform. Distribution Because deno desktop is built on the same machinery as deno compile, the output is a standalone binary with your code and assets embedded.

§3 AI · 97%

The format follows the extension you pass to --output: .app and .dmg on macOS, .exe or an .msi installer on Windows, and .AppImage, .deb, or .rpm on Linux. You don’t need a fleet of machines to ship cross-platform, though. --target cross-compiles the app to any supported platform and --all-targets builds them all in one command, so a single Linux CI runner (or your laptop) can turn out binaries for Windows, macOS, and Linux together. The Windows .msi and Linux .deb / .rpm installers are authored in pure Rust, so they’re produced from any host with no platform-specific packaging toolchain: $ deno desktop --output MyApp.dmg main.ts $ deno desktop --target x86_64-pc-windows-msvc main.ts $ deno desktop --all-targets main.ts The five supported targets match deno compile: Linux x64/arm64, Windows x64, and macOS x64/arm64. For smaller artifacts, --compress ships the runtime and UI backend as a self-extracting bundle that unpacks on first launch. For the full guides, see the deno desktop documentation. And for a complete, real-world example, denidian is a note-taking app built with deno desktop: Performance Deno 2.9 ships broad performance gains in startup time, memory use, and HTTP throughput. The Deno.serve benchmarks below run three workloads at concurrency 100: a plaintext Hello, World!, a 1 MiB response body, and a realworld request that POSTs a JSON payload with a Bearer-auth header and echoes it back as JSON.

§4 AI · 77%

All measured on a dedicated x86_64 Linux box against Deno 2.8.0: Deno 2.8 (gray) vs 2.9 (blue) Cold startlower is better v2.834.2 ms v2.917.3 ms 1.98x faster Deno.serve realworldhigher is better v2.856.8k req/s v2.972.4k req/s 1.27x faster Deno.serve plaintexthigher is better v2.877.0k req/s v2.985.6k req/s 1.11x faster Deno.serve 1 MiB bodyhigher is better v2.81,617 req/s v2.91,907 req/s 1.18x faster RSS, realworldlower is better v2.8142 MB v2.964 MB 2.2x less memory RSS, 1 MiB bodylower is better v2.8197 MB v2.963 MB 3.1x less memory Deno.serve throughput and peak RSS at concurrency 100; cold start is mean of 150 hyperfine runs. Dedicated x86_64 Linux box, server and load generator pinned to disjoint cores, oha median of 3 runs. Startup. A hello-world program now cold-starts in about half the time it took in 2.8 (34ms down to 17ms). The win comes from lazy-loading node: globals out of the snapshot, gating the eager Node bootstrap to Node workers, a V8 code cache for residual lazy-loaded ESM modules, and a minified snapshot (#34450, #35373, #35338, #35183); on macOS, chained fixups trim additional pre-main time (#35409). Memory. The standout this cycle is memory under load. In 2.8, resident set size grew with the workload, from roughly 94 MB serving plaintext up to 197 MB streaming 1 MiB bodies.

§5 AI · 85%

In 2.9 it stays essentially flat, holding around 62 MB no matter what the server is doing. That works out to 2.2x less peak RSS on the realworld workload (142 MB down to 64 MB) and 3.1x less on 1 MiB bodies (197 MB down to 63 MB), so the same machine can run far more concurrent Deno.serve instances before it runs out of headroom. HTTP throughput. Deno.serve is faster across the board too: the realworld workload gains 1.27x, plaintext 1.11x, and 1 MiB bodies 1.18x, helped by a new Deno-owned HTTP/1.1 serving path (#34446). Several hot paths also moved from JavaScript into Rust this release: crypto.subtle (#34966) and console / Deno.inspect (#35087). CSS module imports Deno 2.9 supports importing CSS files as constructable stylesheets using import attributes, matching the CSS module scripts web standard (#35093): main.tsimport sheet from "./styles.css" with { type: "css" }; document.adoptedStyleSheets = [sheet];The import evaluates to a CSSStyleSheet instance, so the same code runs in Deno and in the browser without a bundler step. It’s gated behind the --unstable-raw-imports flag in 2.9. A lone CSS import isn’t much on its own, but it’s the difference between front-end code that runs under Deno and code that trips the module loader: components and modules that import their own stylesheets now load and type-check directly, which makes testing front-end code in Deno considerably easier. Learn more about modules. Migrating from npm, pnpm, yarn, and Bun Moving an existing Node project to Deno is about as smooth as it gets: in most cases it’s a couple of commands. Run deno install to pull your dependencies and deno task dev to start your app, and you’re running on Deno. There’s nothing to port and nothing to rewrite.

§6 AI · 81%

Deno reads the package.json, lockfile, and workspace layout you already have, and 2.9 closes the last rough edges so that even pnpm workspaces and tools that shell out to node work without intervention. Your lockfile comes with you. The biggest friction in switching package managers is losing a carefully-pinned dependency graph. In 2.9 you don’t. Run deno install in a project that has a package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock but no deno.lock, and Deno seeds a fresh deno.lock straight from it, carrying over the exact resolved versions and integrity hashes on that first install (#34296, #35394): $ deno install Seeded deno.lock from package-lock.json There’s no re-resolution and no surprise upgrades: the versions you were running under npm are the versions you run under Deno. From there deno install writes a node_modules directory Deno can run against, and deno task runs the package.json scripts you already have, so the rest of your team can keep working the way they do. Workspaces carry over, pnpm’s included. Deno already understands the workspaces field that npm, yarn, and Bun keep in package.json, so those monorepos work as-is. pnpm is the odd one out: it stores its workspace configuration in a separate pnpm-workspace.yaml that Deno doesn’t read, which used to surface as a confusing resolution error. Now Deno spots that file and migrates its packages, catalog, and catalogs into your package.json (or deno.json) without disturbing your comments or existing fields, then asks you to re-run (#34993). Combined with the catalog: protocol Deno adopted in 2.8, your centralized, shared dependency versions keep working after the move. Tools that expect node keep working. Plenty of build tooling shells out to a node binary directly, like Next.js’s Turbopack worker pool. When no real node is installed, Deno now puts a stand-in on PATH that forwards to itself and translates Node’s CLI arguments, so those tools run unmodified. A real node is never shadowed, and DENO_DISABLE_NODE_SHIM=1 opts out (#34969).