Deno 2.8

, six new subcommands ( , , , , , ), network debugging in Chrome DevTools, framework aware , and 3.66x faster cold npm installs.

Bartek Iwańczuk ۲۷ دقیقه مطالعهمتن اصلی

Deno 2.8 is here. This is our biggest minor release to date and we’re excited to share it with you.

To upgrade to Deno 2.8, run the following in your terminal:

deno upgrade

If Deno is not yet installed, run one of the following commands to install or learn how to install it here.

# Using Shell (macOS and Linux):
curl -fsSL https://deno.land/install.sh | sh

# Using PowerShell (Windows):
iwr https://deno.land/install.ps1 -useb | iex

New subcommands

deno audit fix

deno audit (shipped in 2.6) reports vulnerabilities in npm packages in your dependency tree. The new

deno audit fix

subcommand goes one step further and automatically upgrades affected packages to the nearest patched version that still satisfies your version constraints (#32909, #34273). The same behavior is also available as a

--fix

flag on

deno audit

:

$ deno audit fix
╭ body-parser vulnerable to denial of service when url encoding is enabled
│ Severity:   high
│ Package:    body-parser
│ Vulnerable: <1.20.3
╰ Info:       https://github.com/advisories/GHSA-qwcr-r2fm-qrc7

╭ Express.js Open Redirect in malformed URLs
│ Severity:   moderate
│ Package:    express
│ Vulnerable: <4.19.2
╰ Info:       https://github.com/advisories/GHSA-rv95-896h-c2vc

Found 2 vulnerabilities
Severity: 0 low, 1 moderate, 1 high, 0 critical

Fixed 1 vulnerability:
  body-parser 1.19.0 -> 1.20.3

1 vulnerability could not be fixed automatically:
  express (major upgrade to 5.0.0)

Anything that needs a major-version bump is listed separately, so you can decide whether to relax the constraint. Learn more about deno audit fix.

deno bump-version

deno bump-version

updates the version field in your

deno.json

or

package.json

(#30562):

$ deno bump-version patch         # 1.4.6 -> 1.4.7
$ deno bump-version minor         # 1.4.6 -> 1.5.0
$ deno bump-version major         # 1.4.6 -> 2.0.0
$ deno bump-version prerelease    # 1.4.7-0 -> 1.4.7-1

In a workspace it does more. Run it at the workspace root and the same increment is applied to every member package, with matching

jsr:

version constraints in the root config and import map rewritten in place so cross-package references stay in sync (#33689):

$ deno bump-version patch    # bumps every workspace member

Without an increment argument, workspace mode switches to deriving per-package bumps from Conventional Commits between a base ref and the current branch. It honors scoped commits, wildcard

*

scopes,

BREAKING

/

!

for major bumps, prerelease increments, and 0.x.y semver semantics, and treats any manual version edits since the base ref as authoritative.

$ deno bump-version --base=main --dry-run
--dry-run

prints the planned changes without writing anything, and

--start

/

--base

let you pin the comparison range when the default “current branch since the latest tag” isn’t what you want.

Learn more about deno bump-version.

deno ci

CI scripts and Dockerfiles want one thing from an install: “give me exactly what the lockfile says, and fail loudly if anything is off.” Until now that meant remembering the right combination of flags on

deno install

. Deno 2.8 adds a dedicated deno ci subcommand (#34235):

$ deno ci

It errors if

deno.lock

is missing, removes any existing

node_modules

directory, and then runs the install with

--frozen

so the lockfile must match the config file exactly. Drop it into your CI step or

Dockerfile

and you get an obvious, greppable signal of “reproducible install” without having to think about flags.

--prod

and

--skip-types

work the same way they do on

deno install

.

deno pack

deno pack

is closer to

tsc
npm pack

combined than to

npm pack

alone: it builds a Deno or JSR project into an npm-publishable tarball in one shot (#32139). Given a

deno.json

like:

{
  "name": "@scope/my-lib",
  "version": "1.0.0",
  "exports": "./mod.ts"
}

…running

deno pack

produces a

scope-my-lib-1.0.0.tgz

that’s ready for

npm publish

. The tarball contains:

  • A generated package.json with type: "module", conditional exports (types/import/default), and the extracted runtime dependencies.
  • Your TypeScript transpiled to JavaScript.
  • .d.ts declaration files extracted via the same fast-check pipeline deno publish uses (pass --allow-slow-types to skip).
  • README and LICENSE files if present in the project root.

Along the way

deno pack

rewrites specifiers so the published package works inside the npm ecosystem:

jsr:@std/path

becomes

@jsr/std__path

,

npm:express@4

becomes

express

, relative

./utils.ts

imports become

./utils.js

, and

node:

builtins are left alone.

Deno.*

API calls are left as-is; packages that rely on Deno APIs will need to polyfill them separately if targeting Node.

File selection is graph-based: only modules reachable from your declared

exports

are bundled, not whatever sits in the directory. Tarballs are deterministic (sorted entries, fixed timestamps and permissions), which matters for reproducible builds and content-addressed registries.

$ deno pack                            # build the tarball
$ deno pack --dry-run                  # preview the file list
$ deno pack --set-version 2.0.0        # override version without editing deno.json
$ deno pack --output my-package.tgz    # write to a specific path
$ deno pack --ignore=tests/            # exclude test files
$ deno pack --allow-dirty              # pack with uncommitted changes

deno transpile

A new subcommand strips types from TypeScript, JSX, and TSX and writes plain JavaScript to disk. No bundling, no module rewriting, no config. Just the emit step.

interface User {
  name: string;
  balance: number;
}

export function greet(user: User): string {
  return `Hello ${user.name}, you have $${user.balance.toFixed(2)}`;
}
$ deno transpile greeter.ts -o greeter.js
export function greet(user) {
  return `Hello ${user.name}, you have $${user.balance.toFixed(2)}`;
}
deno transpile

accepts multiple files,

--outdir

for batch output,

--source-map separate|inline

, and

--declaration

to emit

.d.ts

alongside the JS. Useful when you need to publish a JS-only artifact or pre-build TS for a runtime that doesn’t speak it natively.

Learn more about deno transpile.

deno why

deno why <package>

explains why a package is installed by walking from your direct dependencies down to the package in question (#32908). It’s the equivalent of

npm explain

/

pnpm why

/

yarn why

. It works with both npm and JSR dependencies (#34227).

Given a project that mixes both registries:

{
  "imports": {
    "express": "npm:express@^4",
    "dax": "jsr:@david/dax@^0.43"
  }
}
deno why

traces an npm transitive back to its npm entry point:

$ deno why qs
[email protected]
  npm:express@4 > [email protected]

[email protected]
  npm:express@4 > [email protected] > [email protected]

…and a JSR transitive back to its JSR entry point, with each path through the tree listed separately:

$ deno why @std/path
@std/[email protected]
  jsr:@david/[email protected] > @std/[email protected]
  jsr:@david/[email protected] > @david/[email protected] > @std/[email protected]
  jsr:@david/[email protected] > @std/[email protected] > @std/[email protected]
  jsr:@david/[email protected] > @david/[email protected] > @std/[email protected] > @std/[email protected]

Pin to a specific version with

deno why [email protected]

or

deno why @std/[email protected]

when you only care about one branch of the tree. Learn more about deno why.

Deno now defaults to npm:

Deno 2.8 drops the

npm:

prefix requirement at the CLI:

deno add

and

deno install

now treat unprefixed names as npm packages by default (#33246), so the command you type matches what every Node developer already types out of muscle memory.

# Before 2.8
$ deno add express
error: express is missing a prefix. Did you mean `deno install npm:express`?

# 2.8
$ deno add express
Add npm:[email protected]

Dependencies:
+ npm:[email protected]

The

npm:

prefix still works (and is still required in

import

specifiers), but you don’t have to type it at the CLI. JSR packages keep the

jsr:

prefix so the two registries stay unambiguous.

With this change

deno install

becomes a drop-in for

npm install

,

yarn

, or

pnpm install

in an existing Node project. It reads

package.json

, writes a compatible

node_modules

layout, and installs 3.66x faster than 2.7 on a cold cache; warm installs are faster still thanks to Deno’s shared global cache across projects. Reach for Deno as your package manager and keep running everything else on Node. Learn more about deno install.

Node.js API compatibility

Node.js compatibility has been an important focus for us in the past couple years. And we’re happy to announce that we made a huge leap forward in Deno 2.8: pass rate against Node’s own test suite jumped from roughly 42% in Deno 2.7 to 76.4% in Deno 2.8 (3,405 of 4,457 tests passing); 500 commits landed since Deno 2.7, touching nearly every

node:

module.

We keep close track of this percentage at node-test-viewer.deno.dev:

Node.js test suite pass rate over time on Linux, Windows, and Darwin, climbing from ~42% in January 2026 to the low-to-mid 70s by May 2026.

Head-to-head against Bun 1.3.14 on the same suite:

Deno v2.8

Deno 2.8 72.4%(3,229 / 4,457) vs

Bun 1.3.14 36.4%(1,623 / 4,457).

Deno 2.8 also makes Node compatibility cheaper in real projects: many Node built-in modules are now lazy-loaded, so programs that don’t touch them start faster (importing one of those modules later pays a small deferred load cost). Several

node:*

hot paths also picked up dedicated optimizations; see the Performance section below for benchmark numbers.

Performance

Deno 2.8 ships meaningful speedups across the package manager,

node:*

compatibility, HTTP serving, and the Web platform. Measured on Linux against Deno 2.7.1:

lower is better

3,319 ms

v2.8

906 ms

node:buffer

base64

lower is better

2,594 ms

v2.8

844 ms

node:http

throughput

higher is better

8,339 req/s

v2.8

18,431 req/s

node:crypto

scrypt

lower is better

1,533 ms

v2.8

724 ms

node:http

p99 latency

lower is better

20.86 ms

v2.8

11.89 ms

node:http

chunked writes

higher is better

6,635 req/s

v2.8

11,521 req/s

lower is better

25.39 ms

v2.8

15.68 ms

node:fs

recursive

cpSync

lower is better

432 ms

v2.8

290 ms

MessagePort

ping-pong

lower is better

1,678 ms

v2.8

1,270 ms

hyperfine

samples. HTTP benchmarks: 10 samples of 30-second

oha

runs.

Cold npm installs. An entrypoint importing React, Vite, Babel parser, and ESLint installs 3.66x faster in Deno 2.8,

3,319ms

down to

906ms

, on a fresh

DENO_DIR

. Across 30 samples, the bootstrap 95% confidence interval for the speedup was

3.53x

to

3.75x

. A few of the changes that fed into that number:

  • Abbreviated packuments (#32364). The npm registry exposes a smaller “abbreviated” metadata document (application/vnd.npm.install-v1+json) that includes only the fields a resolver needs; Deno now uses this smaller document for resolution and only fetches the full packument if it needs to.
  • Parallel npm resolution (#32416). The resolver used to walk parent nodes one at a time. Deno 2.8 fans out across parent nodes too, so independent branches of the dependency tree no longer wait on each other.
  • Decompression off the async event loop (#32400). Large packument gzip decompression could stall other HTTP/2 streams sharing the same connection. Deno 2.8 routes registry-body decompression through a blocking thread pool, freeing the event loop for more concurrent requests.
  • Tarball extraction split into CPU and I/O phases (#32408). Tarball extract used to be a single tight loop. It now splits into a CPU-bound decompression phase and an I/O-bound filesystem write phase. Pairs with libdeflater + a preallocated buffer (#32511) (a faster gzip decoder than the stock flate2) and fewer syscalls during tarball extraction (#32541).

node:http. Hello-world

node:http

more than doubles throughput (

2.21x

) and cuts p99 latency by roughly 40%; chunked responses see comparable gains (

1.74x

throughput,

1.62x

tail latency).

base64 across the board. A single change, switching

base64

encode/decode to simdutf (#32743), drives 3.07x faster node:buffer base64 (

2,594ms

down to

844ms

) and the same kind of speedup for

atob

/

btoa

and every Web API path that touches base64.

Other node:* hot paths.

scryptSync

from

node:crypto

is now

2.12x

faster, Rust-backed recursive

node:fs
cpSync

gets

1.49x

faster, and exchanging messages over

MessagePort

between Workers is now

1.32x

faster.

Deno.serve. Native

Deno.serve

got a direct dispatch into the JS handler, a fast path for fully-buffered response bodies, and lighter

Vary

handling (#33845, #33844, #33892). A hello-world benchmark sees

1.13x

increase in throughput and

1.20x

lower median p99 latency.

Other optimizations. A pile of smaller wins that show up everywhere:

  • TextEncoder / TextDecoder fast paths for ASCII / Latin-1 / short strings (#32735, #33674, #33675, #34055).
  • Linear-time set / delete on FormData, URLSearchParams, and Headers (#33961), no more quadratic blowups on large header sets.
  • URLPattern ops drop serde overhead and GC pressure (#32766), so middleware that matches every request gets cheaper.
  • Zero-copy V8-to-Rust string conversion in op slow-paths (#32688) and a SIMD ASCII fast path for op_decode (#33720), which Response.text(), File.text(), and FormData parsing all ride on.
  • V8 thread pool capped at 4 threads (#33697), trimming ~1 MB RSS on a typical desktop.
  • malloc_trim after module loading (#32662) and on Worker termination (#32617), fixing 3–5x RSS bloat on Linux when loading large TypeScript codebases.

import defer

Deno now supports the TC39 import defer proposal: a module can be loaded and parsed without running its top-level code. The module is then only evaluated the first time you touch one of its exports (#32360).

This feature is useful for trimming startup time when a module is expensive to evaluate but rarely used on a given codepath.

console.log("deferred module evaluated");
export const value = 42;
import defer * as deferred from "./deferred.js";

console.log("before access");
console.log(`value: ${deferred.value}`);
console.log("after first access");
$ deno run main.js
before access
deferred module evaluated
value: 42
after first access

The

deferred module evaluated

line lands between

before access

and the property read. Module evaluation is delayed until something actually needs it.

The same semantics are available with

import.defer()

for dynamic imports. A common pattern: pre-load both branches of a decision, but only evaluate the one you actually pick.

console.log("PNG decoder evaluated");
export function decode(bytes) {/* ... */}
console.log("JPEG decoder evaluated");
export function decode(bytes) {/* ... */}
const png = await import.defer("./png-decoder.js");
const jpeg = await import.defer("./jpeg-decoder.js");

const format = Deno.args[0]; // "png" or "jpeg"
const decoder = format === "png" ? png : jpeg;

const bytes = Deno.readFileSync(`input.${format}`);
console.log(decoder.decode(bytes));
$ deno run -R main.js png
PNG decoder evaluated

Both modules are fetched and parsed up front, but only the selected one is evaluated.

import defer

Works in

.ts

and

.tsx

files with no extra setup.

deno check

and the LSP both understand the syntax:

import defer * as deferred from "./deferred.ts";

const n: number = deferred.value;

Learn more about import defer.

TypeScript 6.0.3

The bundled TypeScript compiler is updated to 6.0.3 (#32944). 6.0 is a transition release the TypeScript team uses to land breaking changes and deprecations before the native-port 7.0 ships; see Microsoft’s Announcing TypeScript 6.0 post for the full list.

deno check

,

deno bundle

, the LSP, and

deno compile

all use the new version automatically. No flag, no config change.

lib.node included by default

deno check

and the LSP now include

lib.node

in every type-check by default (#33823). Before 2.8 you had to add

"node"

to

compilerOptions.lib

in

deno.json

(or sprinkle

/// <reference types="node" />

across files) to get

NodeJS.*

,

Buffer

,

process

, and the rest of Node’s ambient types to resolve. Now they’re just there:

// 2.8: type-checks with no `compilerOptions.lib` configuration
const buf: Buffer = Buffer.from("hello");
const t: NodeJS.Timeout = setTimeout(() => {}, 0);
console.log(process.versions.node);
lib.node

is implemented on top of

@types/node

, and Deno pulls that package from npm whose major version matches the Node release Deno reports in

process.versions.node

. Today that’s the Node 24.x types, in line with the version returned by:

console.log(process.versions.node); // e.g. 24.2.0

If you’d rather pin a different version of

@types/node

(for example because your project standardizes on Node 22, or because you need a newer patch), just declare it as a dependency and Deno will use yours instead of the bundled copy:

{
  "devDependencies": {
    "@types/node": "^22.10.0"
  }
}
{
  "imports": {
    "@types/node": "npm:@types/node@^22.10.0"
  }
}

In practice the default behavior means npm packages with Node-typed APIs (i.e. nearly all of them) type-check cleanly when imported via

npm:

, and library authors writing for both runtimes can rely on

NodeJS.Timeout

,

Buffer

, and friends without telling Deno consumers to configure their TypeScript.

The trade-off is that Node-only globals like

process

and

Buffer

are now in scope at the type level even when you don’t want them, which can quietly encourage code that won’t run in the browser. The no-process-global and no-node-globals lint rules used to be on by default to catch this; in 2.8 they are off by default but still available (#33247). Re-enable them in

deno.json

if your project targets multiple runtimes:

{
  "lint": {
    "rules": {
      "include": ["no-process-global", "no-node-globals"]
    }
  }
}

The runtime globals themselves are unchanged. This is purely a type-level addition. Learn more about including Node types.

Debugging

A major addition to debugging capabilities in 2.8 is that Chrome DevTools can now inspect Deno’s network traffic. Run your program with

--inspect-wait

(or

--inspect

/

--inspect-brk

), open

chrome://inspect

in Chromium, click Inspect on the Deno target, and the DevTools Network tab now shows every

fetch()

,

node:http

/

node:https

client request, and

WebSocket

your program makes (including server-side WebSockets opened via

Deno.upgradeWebSocket()

), with request and response headers, status codes, bodies, and timing, exactly the way you’d see network traffic in a browser tab.

const res = await fetch("https://api.github.com/repos/denoland/deno");
console.log(res.status, (await res.json()).stargazers_count);
$ deno run --inspect-wait --allow-net server.ts
Debugger listening on ws://127.0.0.1:9229/...
Visit chrome://inspect to connect to the debugger.
Deno is waiting for debugger to connect.

DevTools Network tab showing a fetch() request to api.github.com with the JSON response previewed inline.

Under the hood this required implementing the Network CDP domain on the inspector side and wiring

fetch()

,

node:http

, and

WebSocket

into it on the runtime side:

The same events also surface through

node:inspector

for programmatic clients and through any other CDP frontend (VS Code’s JavaScript debugger, the standalone

chrome-devtools-frontend

, etc.), so tooling that already speaks CDP against Node can attach to Deno without changes. Learn more about inspecting network traffic.

CPU profiling

Deno 2.8 ships a built-in CPU profiler that matches Node’s --cpu-prof flag, plus a few extras. Start the profiler with

--cpu-prof

and Deno writes a V8 CPU profile to disk when the program exits (#31909):

$ deno run --cpu-prof main.ts
$ ls
CPU.20260519.022823.34721.0.001.cpuprofile  main.ts

The

.cpuprofile

file opens directly in Chrome DevTools (Performance panel, then Load profile) or any tool that speaks the V8 profile format (eg. V8’s profview). For times when you don’t want to load a profile into a UI, two new output formats land alongside it:

  • --cpu-prof-flamegraph writes a self-contained, interactive SVG you can open in any browser, no extra tooling required (#32572).
  • --cpu-prof-md writes a human-readable Markdown report with the hottest functions, the call tree, and per-function details.

Combine them in a single run:

$ deno run --cpu-prof --cpu-prof-flamegraph --cpu-prof-md main.ts
$ ls
CPU.20260519.022823.34721.0.001.cpuprofile
CPU.20260519.022823.34721.0.001.svg
CPU.20260519.022823.34721.0.001.md

The Markdown report is the fastest way to triage a slow run from a terminal:

# CPU Profile

| Duration | Samples | Interval | Functions |
| -------: | ------: | -------: | --------: |
| 187.81ms |      74 |   1000us |        34 |

**Top 10:** `fib` 100.0%

## Hot Functions (Self Time)

|  Self% |    Self | Total% |   Total | Function | Location  |
| -----: | ------: | -----: | ------: | -------- | --------- |
| 100.0% | 72.00ms | 100.0% | 72.00ms | `fib`    | main.ts:1 |

The SVG flamegraph is interactive. Click any frame to zoom, hover to see exact timings:

Learn more about CPU profiling in Deno.

Package and workspace management

catalog: protocol

Monorepos that share dependency versions across packages used to require manual coordination: every member’s

package.json

had to be updated in lockstep when a shared dep was bumped. Deno 2.8 adopts pnpm’s catalog: protocol, letting you declare versions once in the workspace root and reference them by name from each member (#32947).

Declare a default catalog in the workspace root:

{
  "workspace": ["./packages/api", "./packages/web"],
  "catalog": {
    "hono": "^4.6.0",
    "zod": "^3.23.0"
  }
}

Then reference it from any member with the bare

catalog:

specifier:

{
  "name": "api",
  "dependencies": {
    "hono": "catalog:",
    "zod": "catalog:"
  }
}

For projects that need multiple catalogs (e.g. one for production, one for build tooling), use named catalogs under the plural

catalogs

field:

{
  "workspace": ["./packages/api"],
  "catalogs": {
    "runtime": { "hono": "^4.6.0" },
    "tools": { "typescript": "^6.0.0" }
  }
}
{
  "dependencies": { "hono": "catalog:runtime" },
  "devDependencies": { "typescript": "catalog:tools" }
}
catalog:

also resolves correctly inside

package.json

overrides (#33799) and inside workspaces declared in the object form (#33816), so the protocol works the same way no matter where you reach for it. Learn more about the catalog: protocol.

Cross-platform npm installs

A lot of popular npm packages ship platform-specific native binaries via

optionalDependencies

: esbuild, sharp, rollup, the SWC family, and so on. Deno reads the

os

and

cpu

fields declared on each optional dependency, compares them to the host platform, and only fetches the binary you can actually run. Installs stay small, fast, and free of untrusted binaries from platforms you don’t ship to.

The trade-off shows up when you do want a different platform: building a Linux ARM64 Docker image from a macOS dev laptop, prepping a CI artifact for Windows, or pre-populating a cache for a deploy target. The new

--os

and

--arch

flags on

deno install

tell the resolver to pretend it’s on a different host, mirroring

npm install --os --cpu

(#32785):

$ deno install --os=linux --arch=arm64

Supported

--os

values:

aix

,

android

,

darwin

,

freebsd

,

linux

,

openbsd

,

sunos

,

win32

. Supported

--arch

values:

arm

,

arm64

,

ia32

,

mips

,

mipsel

,

ppc

,

ppc64

,

s390

,

s390x

,

x64

. Learn more about cross-platform installs.

--prod flag

Production deploys rarely need

devDependencies

or

@types/*

packages. Until now

deno install

always pulled them in anyway, padding the install size and adding npm packages you don’t actually ship. The new

--prod

flag skips both (#33248):

$ deno install --prod

Drop it into your

Dockerfile

or CI release step and your production image gets only the dependencies it needs to run. Learn more about --prod.

Hoisted node_modules

Deno’s default

node_modules

layout is isolated: each package gets its own symlink-resolved tree, so it can only see the dependencies it explicitly declared. That’s the right default for new projects, but some legacy npm tooling assumes the flat, hoisted layout that

npm install

produces, where every package lives at the top level of

node_modules

and can

require()

anything it finds.

deno.json

gets a new

nodeModulesLinker

field for those cases (#32788):

{
  "nodeModulesDir": "manual",
  "nodeModulesLinker": "hoisted"
}

Valid values are

"isolated"

(the default) and

"hoisted"

. Use the latter when porting an existing Node project that relies on the npm-style layout. Learn more about isolated vs. hoisted node_modules.

.npmrc support

Several gaps in Deno’s

.npmrc

handling were closed this release.

min-release-age is the headline addition. The feature itself shipped in 2.6: Deno refuses to install a package version younger than the configured age, which catches the vast majority of npm supply-chain attacks before they land in your tree (malicious versions are typically detected and yanked within a few days of publishing). In 2.8 you can also configure it from

.npmrc

, matching the npm convention so existing tooling keeps working (#33983):

min-release-age=72h

The remaining improvements unblock common authentication and configuration scenarios with private registries:

  • certfile and keyfile for mutual-TLS authentication (#32655)
  • email field on _auth entries, used by some legacy on-prem registries (#32616)
  • NPM_CONFIG_REGISTRY correctly overrides the registry declared in .npmrc (#32394)

Learn more about .npmrc configuration.

file:

and

link:

specifiers point at a local path on the publisher’s machine and only make sense during development. Plenty of published npm packages still ship with one accidentally left in their

package.json

:

{
  "name": "some-package",
  "version": "1.2.3",
  "dependencies": {
    "lodash": "^4.17.0",
    "local-helpers": "file:../local-helpers"
  }
}

That stray

file:

entry used to break Deno with a cryptic error during resolution:

$ deno install
error: Invalid version requirement. Unexpected character.
  [email protected] -> local-helpers

In 2.8,

file:

and

link:

specifiers are silently skipped while parsing registry metadata, so packages that carry stray local-path deps install cleanly (#32876). The actual code those deps reference is bundled into the published tarball anyway, so nothing’s lost by ignoring them. Learn more about file: and link: dependencies.

--package-json flag

Projects migrating from Node often end up with both a

package.json

and a

deno.json

. By default,

deno add

,

deno install

,

deno remove

, and

deno uninstall

modify

deno.json

because that’s where Deno-native projects keep their dependencies. The new

--package-json

flag forces those subcommands to target

package.json

instead, useful when you want the npm-style manifest to remain the source of truth for your team’s tooling (#33199):

$ deno add --package-json express
$ deno install --package-json
$ deno remove --package-json lodash

Learn more about --package-json.

Bug fixes

Deno’s package management code is a moving target. Every release brings a lot of small correctness fixes, and 2.8 is no exception: 35 of them landed. A few worth calling out explicitly:

  • Peer dependency resolution got two meaningful improvements: a fix for cases where a peer dep ended up installed in multiple conflicting versions and caused hangs (#32358), and memoization of peer-cache hit checks that previously blew up combinatorially on large workspaces (#32609).
  • Aliased package.json dependencies ("my-name": "npm:foo@1") are now all linked into node_modules, not just the canonical one (#33068).
  • Global installs regenerate their lockfile correctly when you pass --force (#33970).
  • deno update --lockfile-only no longer rewrites your config file alongside the lockfile, restoring the contract of the --lockfile-only name (#33746).

deno compile updates

deno compile

keeps moving toward “point it at a project, get a binary back” as the default workflow. Two new features cover most of that ground in 2.8, plus a batch of fixes for binaries that re-launch themselves the way many npm published CLIs do.

Framework detection

Running

deno compile .

(or

deno compile ./myapp

) now auto-detects the web framework you’re using, runs

deno task build

to produce build output, and generates the right entrypoint for it (#33164). The supported list covers most of the popular options: Next.js, Astro, Fresh, Remix, SvelteKit, Nuxt, SolidStart, TanStack Start, and Vite SSR.

$ deno compile .
Compile file:///project/main.ts to file:///project/myapp
Detected Vite SSR project
Running deno task build...
...

Entrypoints use

import.meta.dirname

so paths resolve against the virtual filesystem inside the compiled binary, which means a Next.js or Astro build shipped as a single executable now works without a separate runtime wrapper. Learn more about framework detection.

Other improvements

For projects with large npm dependency trees

deno compile

used to go silent for tens of seconds. It now reports progress through each phase (#33874): an animated progress bar in an interactive terminal, or per-phase log lines in CI and piped output. Operations that finish in under 120ms render nothing, so fast paths stay quiet.

A batch of fixes targets compiled npm CLIs that re-launch themselves (e.g.

@google/gemini-cli

).

child_process.spawn

and

child_process.fork

skip the Node-to-Deno CLI argument translation when running inside a standalone binary (#32980), the duplicate exe path is stripped from

argv

when a standalone binary relaunches itself (#33016), and

process.argv[1]

now resolves to

Deno.execPath()

in compiled binaries instead of the entrypoint URL (#32990). The self-extracting cache directory also moves to a hidden directory next to the executable (#32329) so it no longer clutters the binary’s parent directory.

A few smaller fixes round out the section:

--env-file

resolves parent-directory paths again and a missing env file no longer aborts

deno compile

(#32686); bundling CSS treats same-document fragment URLs as external (#33492); and

Deno.bundle

reports a clearer error when called from inside a compiled binary (#33503).

OpenTelemetry

Deno’s built-in OpenTelemetry integration gets two new exporters and a way to route permission audits straight into your OTel pipeline.

Console exporter

Set

OTEL_EXPORTER_OTLP_PROTOCOL=console

to print spans, logs, and metrics to stderr in a human-readable format. No collector required. Handy when you’re debugging instrumentation locally (#32717).

$ OTEL_DENO=true OTEL_EXPORTER_OTLP_PROTOCOL=console deno run -A main.ts
SPAN outer span [00000000000000000000000000000001/0000000000000001] Internal 1ms
  scope: example-tracer
SPAN inner span [00000000000000000000000000000001/0000000000000002] Internal 0ms
  parent: 0000000000000001
  scope: example-tracer
  key: value
LOG [INFO] "hello from inner"
  scope: deno
  trace: 00000000000000000000000000000001/0000000000000002

gRPC OTLP exporter

The OTLP exporter now speaks gRPC alongside the existing HTTP/protobuf transport. Point it at your collector’s gRPC port and you’re done (#30365).

$ OTEL_DENO=true \
  OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
  OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com:4317 \
  deno run -A main.ts

Permission audits as OTel logs

The permission audit log (introduced in 2.5) can now be fed straight into your OTel exporter. Set

DENO_AUDIT_PERMISSIONS=otel

and every permission check becomes an OTel log event correlated with the surrounding span, so you can alert on unexpected file or network access across your fleet without scraping JSONL files (#32501).

$ OTEL_DENO=true DENO_AUDIT_PERMISSIONS=otel deno run -A main.ts

Learn more about OpenTelemetry in Deno.

Other improvements

  • Span attributes copied from HTTP requests onto per-route metrics (#32720)
  • Array values supported in OTel attribute maps (#32748)
  • Server spans for 4xx responses no longer marked as errors (#32722)
  • log.iostream attribute added to console logs (#32723)
  • exception.* attributes added to OTel log records (#32726)

Testing and coverage

Deno’s built-in test runner and coverage tool both pick up a few useful improvements this release.

Sanitizers off by default

The

sanitizeOps

and

sanitizeResources

options on

Deno.test()

now default to

false

instead of

true

(#33250). These sanitizers fail a test when async ops or resources outlive it, and in practice they have been a frequent source of confusing failures, especially for code that uses

setTimeout

,

node:http

, or other APIs whose cleanup is loosely scoped. The new default matches what most people expect: tests pass when their assertions pass, and you opt back into the stricter behavior when you actually want it.

You can re-enable sanitizers for a single test:

Deno.test(
  "leaks a timer",
  { sanitizeOps: true, sanitizeResources: true },
  () => {
    setTimeout(() => {}, 1000);
  },
);

…for every test in a file via the new module-level API:

// Apply to every Deno.test() in this file.
Deno.test.sanitizer({ ops: true, resources: true });

Deno.test("leaks a timer", () => {
  setTimeout(() => {}, 1000);
});

…or globally in

deno.json

:

{
  "test": {
    "sanitizeOps": true,
    "sanitizeResources": true
  }
}

Learn more about test sanitizers.

Per-test timeouts

Deno.test()

now accepts a

timeout

option (in milliseconds) that fails a test if it runs longer than expected, instead of hanging your CI run (#33815):

Deno.test("slow operation", { timeout: 100 }, async () => {
  await new Promise((r) => setTimeout(r, 500));
});
$ deno test
slow operation ... FAILED (102ms)

 ERRORS

slow operation => ./server_test.ts:5:6
error: Test timed out after 100ms.

The timeout is per-test, so a slow test no longer drags down everything else in the suite. Pair it with

--parallel

to keep total wall-clock time predictable. Learn more about test timeouts.

Function coverage

deno coverage

now reports per-function coverage alongside line and branch coverage in both the text summary and the HTML report (#32507). Useful when a file has high line coverage but most of its API surface is untested:

$ deno test --coverage=cov
$ deno coverage cov
| File      | Branch % | Function % | Line % |
| --------- | -------- | ---------- | ------ |
| math.ts   |    100.0 |       66.7 |   66.7 |
| All files |    100.0 |       66.7 |   66.7 |

In this example two of three exported functions in

math.ts

are tested: line and function coverage agree here, but the new column makes it obvious at a glance which functions are missing tests. Line percentage alone can hide that when a file has a few large untested functions. Learn more about function coverage.

Other fixes

  • deno test dedupes test modules discovered through multiple workspace members so each test only runs once (#32380)
  • deno test --watch restarts the suite when an --env-file changes, matching the watch behavior for source edits (#32461)
  • Coverage line and branch counts are now correct in edge cases involving partially overlapping zero-count ranges (#32312)
  • deno coverage warns instead of erroring out when a source file referenced by the profile is missing, so a stale data file doesn’t kill the whole report (#32398)

Web APIs

Deno 2.8 keeps closing the gap with the browser platform, with two new APIs, expanded Web Crypto coverage, and a long tail of fixes and perf wins across

fetch

, streams,

TextEncoder

/

TextDecoder

, and friends.

Canvas and geometry primitives

Two browser-platform APIs that server-side code has long been asking for:

OffscreenCanvas

(#29357) lands as a stable global, and the Geometry Interfaces Module Level 1 spec is implemented behind

--unstable-webgpu

(#27527).

OffscreenCanvas

is the same API the browser exposes for off-thread canvas work: create a canvas without a DOM, get a rendering context, and transfer it between workers via

postMessage

. Deno’s implementation supports the

"bitmaprenderer"

and

"webgpu"

contexts (the

"2d"

and WebGL contexts are not implemented and

getContext

returns

null

for them). A common pattern is to decode an image, place the resulting

ImageBitmap

on the canvas, and encode the canvas back out via

convertToBlob

:

const res = await fetch(
  "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=480",
);
const bitmap = await createImageBitmap(await res.blob());
const { width, height } = bitmap;

const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("bitmaprenderer")!;
ctx.transferFromImageBitmap(bitmap); // consumes the bitmap

const out = await canvas.convertToBlob({ type: "image/png" });
await Deno.writeFile("out.png", new Uint8Array(await out.arrayBuffer()));
$ deno run --allow-net --allow-write=. thumb.ts

That covers headless format conversion, thumbnail generation, and social-card rendering without a headless browser. Pair it with the

"webgpu"

context for off-window GPU-rendered targets.

The geometry interfaces add

DOMPoint

,

DOMRect

,

DOMQuad

, and

DOMMatrix

(plus their

Readonly

variants). These are the same matrix and rectangle types the browser uses for transforms and hit-testing, and they’re the missing primitive for code that wants to share geometry math between the browser and Deno:

const m = new DOMMatrix()
  .translate(100, 50)
  .rotate(30)
  .scale(2);

const p = new DOMPoint(10, 0).matrixTransform(m);
console.log(p.x, p.y); // 117.32050807568878 60

const r = new DOMRect(0, 0, 100, 50);
console.log(r.right, r.bottom); // 100, 50
$ deno run --unstable-webgpu geometry.ts

Learn more about OffscreenCanvas and Geometry Interfaces.

Cloneable and transferable values

The web platform draws a careful line between two ways of moving JavaScript values across realm and worker boundaries: serializable objects can be deep-copied with structuredClone, and transferable objects can have their ownership moved with zero copy. Deno 2.8 closes long-standing gaps in both, so you can finally write efficient multi-threaded programs without serializing state to JSON by hand.

The headline fix is that non-serializable Web types are finally transferable when correctly listed in the

transfer

array of

structuredClone

or postMessage (#33491):

const stream = new ReadableStream({
  start(c) {
    c.enqueue("hello");
    c.close();
  },
});

// Before 2.8: threw DataCloneError.
// 2.8: ownership of `stream` moves into the cloned value.
const cloned = structuredClone({ stream }, { transfer: [stream] });

worker.postMessage({ stream: cloned.stream }, [cloned.stream]);

The full set of types Deno can now transfer is Headers, Request, Response, ReadableStream, WritableStream, and TransformStream. The full set of types Deno can now serialize (deep-copy) is Blob, File, CryptoKey, DOMException, X509Certificate, EventLoopDelayHistogram, and RecordableHistogram.

Learn more about structuredClone and transferable objects.

Other fixes

  • crypto.subtle.digest accepts "SHA3-256", "SHA3-384", and "SHA3-512" (#32342).
  • P-521 (the largest NIST curve) gains first-class support across sign, verify, and ECDH derive (#32602), with EC key export fixed for all formats (#32412, #34087). Importing X25519, X448, and Ed25519 raw keys also validates their length now instead of silently accepting any byte string (#33944).
  • CacheStorage.keys() and Cache.keys() are implemented (#33275), filling in the iteration entry points on the Cache API.
  • AbortSignal.any() no longer leaks memory when wrapping long-lived signals (#32916).
  • subtle.importKey no longer panics on a wrong algorithm name (#32410); it throws as specified.
  • getRandomValues throws TypeMismatchError for non-TypedArray inputs (#33470).
  • fetch retries on stale pooled HTTP/1.1 connections (#32566) and stops mutating the caller’s options in Deno.createHttpClient (#33497). The response resource is closed cleanly when an abort races op completion (#33928), and Node Readable request bodies stream through a byte ReadableStream (#33432).
  • Deno.listenDatagram defaults its hostname to 0.0.0.0 (#33496), matching Deno.listen.
  • HTTP upgrade handlers can now reject with non-101 status codes (#32615), an empty Host header is treated as missing (#33234), and a WebSocket H2 stream reset no longer panics (#33982).
  • MessageEvent ports convert via the WebIDL sequence iteration protocol (#33652), and source is now retained from MessageEventInit (#33500).
  • A few Node-aligned error code touch-ups: ERR_MISSING_ARGS on URL.revokeObjectURL() (#33471), ERR_ILLEGAL_CONSTRUCTOR (#33535) and ERR_INVALID_THIS (#33467) on the corresponding TypeErrors, and AbortSignal.timeout’s error message matches Node’s (#33460).
  • console.log supports the %j JSON format specifier (#32684), and console.dirxml now routes through the log printer (#33443).
  • QuotaExceededError is upgraded to a DOMException-derived interface (#32244), removeEventListener handles a null options argument (#32605), and Event.returnValue respects the cancelable and passive flags (#33651).
  • ReadableStreamBYOBRequest.view is narrowed to Uint8Array (#33477), a late write racing with TransformStream.cancel no longer hangs (#33478), and a WebTransport datagram-overflow infinite loop is fixed (#33075).
  • GPUQueue.writeBuffer() accepts plain ArrayBuffer data sources (#33152).
  • WebSocket response headers handle non-ASCII bytes correctly (#32594).

Task runner

deno task

got a small quality-of-life improvement that matters once you start running tasks in parallel: every output line is now prefixed with the task name that produced it, so interleaved stdout from concurrent tasks no longer reads as one tangled stream (#33805). Given a config that fans out to two builds:

{
  "tasks": {
    "client": "echo building client && sleep 1 && echo client ready",
    "server": "echo building server && sleep 1 && echo server ready",
    "dev": { "dependencies": ["client", "server"] }
  }
}

…running

deno task dev

interleaves the two scripts cleanly:

$ deno task dev
Task client echo building client && sleep 1 && echo client ready
[client] building client
Task server echo building server && sleep 1 && echo server ready
[server] building server
[client] client ready
[server] server ready
deno task

fanning out to three child tasks in parallel, with each output line prefixed and color-coded by task name.

The prefixes are color-coded per task and stay attached even when a task forks subprocesses, so a parallel

build
test
lint

workflow stays legible without piping everything through a separate multiplexer.

The task shell itself also picks up a couple of POSIX features:

set -e

/

set -o errexit

(and

set +e

to turn it back off) aborts the surrounding sequential list on the first non-zero exit, and the POSIX null command

:

is now a builtin. Both make it easier to port shell scripts into

tasks

blocks without resorting to a separate

bash -c

wrapper.

The rest of the changes are workspace fixes that make

--filter

and recursive task discovery match user expectations:

  • --filter now matches a workspace member by directory name as well as package name (#33499), and falls back to the directory name only when an explicit workspace root check passes (#33540)
  • Recursive task name completion works inside workspaces, so shell tab-completion sees tasks declared in any member (#32422)
  • Backticks in arguments forwarded to a task are escaped correctly instead of being re-evaluated by the task shell (#34151)

Learn more about task dependencies and parallel output.

deno upgrade changes

Two improvements to

deno upgrade

worth flagging: it’s a lot smaller over the wire now, and you can use it to grab a Deno build straight from a PR.

Delta updates

deno upgrade

now downloads a binary diff between your current version and the target instead of the full release archive, when one is available (#33274). A typical patch-level upgrade goes from a roughly 48 MB download to 3-6 MB, an 87-93% bandwidth reduction. Useful for everyone, but especially for CI runners and ephemeral environments that pull Deno on every job.

The implementation chains up to three bsdiff patches to reach the target (e.g. 2.7.14 to 2.8.0 to 2.8.1 to 2.8.2). Every patch and every intermediate binary is SHA-256-checked against the published checksums, so a bad delta can’t quietly corrupt your install. If any step fails (missing delta asset, checksum mismatch, patch error), Deno transparently falls back to a full download. The

--no-delta

flag forces the full path if you ever want to bypass deltas entirely. Learn more about delta updates.

Install from a PR

deno upgrade pr <number>

downloads the binary that CI built for a given pull request and replaces your local

deno

with it, so you can try a fix or new feature without building from source (#33252). It uses the gh CLI under the hood, so you need it installed and authenticated:

# Try the binary CI built for PR #34227
$ deno upgrade pr 34227

# Save to a path instead of replacing your current install
$ deno upgrade --output ./deno-pr pr 34227

# See what would happen without doing it
$ deno upgrade --dry-run pr 34227

Under the hood

deno upgrade

queries

gh pr view

, walks the PR’s CI runs to find the right artifact for your platform (preferring release over debug), downloads it, sanity-checks it with

deno -V

, then swaps it in. Faster than a

git clone && cargo build

round-trip when you just want to verify a fix. Learn more about installing a build from a PR.

Module loader hooks

Technically a piece of Node.js API compatibility, but big enough to deserve its own section: Deno 2.8 implements Node’s module.registerHooks() API (#34081). Loader hooks let you customize module loading at runtime: intercept resolution, transform source, redirect specifiers, mock dependencies for tests, or add instrumentation, all without rebuilding Deno or running a separate build step.

As a concrete example, here’s a 14-line loader that teaches Deno how to

import

a

.css

file as the stylesheet text:

import module from "node:module";

module.registerHooks({
  load(url, context, nextLoad) {
    if (!url.endsWith(".css")) {
      return nextLoad(url, context);
    }
    const css = Deno.readTextFileSync(new URL(url));
    return {
      format: "module",
      source: `export default ${JSON.stringify(css)};`,
      shortCircuit: true,
    };
  },
});
.button {
  padding: 8px 16px;
  border-radius: 4px;
  background: #66c2ff;
}

Pre-load the hooks with

--import

so they register before user code runs:

import buttonCss from "./button.css";

console.log(buttonCss);
$ deno run --allow-read --import ./css-loader.ts main.ts
.button {
  padding: 8px 16px;
  border-radius: 4px;
  background: #66c2ff;
}

You could also get a CSS file as text via the standard import attributes proposal (import css from "./button.css" with { type: "text" }) that we shipped back in Deno v2.4 . The point of the example above isn’t .css specifically; it’s showing the mechanics of writing a custom loader for arbitrary file types or transformations.

Loader hooks also work inside binaries produced by

deno compile

, which makes them useful for distributing self-contained CLIs that ship with custom resolution baked in. Here’s a virtual-module loader that exposes a

virtual:greeting

specifier from inside the compiled binary:

import module from "node:module";

module.registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier !== "virtual:greeting") {
      return nextResolve(specifier, context);
    }
    return { url: "virtual:greeting", shortCircuit: true };
  },
  load(url, context, nextLoad) {
    if (url !== "virtual:greeting") return nextLoad(url, context);
    return {
      format: "module",
      source: `export default "Hello from a virtual module!";`,
      shortCircuit: true,
    };
  },
});

const { default: greeting } = await import("virtual:greeting");
console.log(greeting);
$ deno compile -A --output greeter greeter.ts
$ ./greeter
Hello from a virtual module!

We chose not to implement the module.register() API, since Node has deprecated it and plans to remove it.

Learn more about module loader hooks in Deno.

setTimeout and setInterval

A change we’ve been planning for a long time finally lands in 2.8:

setTimeout

and

setInterval

now return Node’s

Timeout

object instead of an opaque number, matching

node:timers

behavior at the global scope (#33249).

This is technically a breaking change, but in practice it should affect very few programs. The common case keeps working untouched:

const t = setTimeout(() => console.log("hi"), 1000);
clearTimeout(t);

The only code that breaks is code that relied on the return value being a

number

, e.g. storing it in a

number

-typed field, doing arithmetic on it, or runtime-checking its type:

// Before 2.8: id was a number
const id: number = setTimeout(fn, 0);

// 2.8: assign to NodeJS.Timeout instead
const id: NodeJS.Timeout = setTimeout(fn, 0);
// Before 2.8: timer was a number, this branch ran
if (typeof timer === "number") {
  clearTimeout(timer);
}

// 2.8: timer is a NodeJS.Timeout object, branch never runs.
// Just clear unconditionally:
clearTimeout(timer);

Why is it worth even this small amount of churn? Three reasons:

  • Performance. The compatibility shim we used to wrap web-style setTimeout around node:timers sat on a hot event-loop path. Removing it cuts overhead on every timer call.
  • Tech debt. Maintaining two parallel timer implementations (web and Node) plus a global proxy that converted between them was a constant source of subtle bugs.
  • Simpler mental model. There is now one global setTimeout in Deno, it behaves the way node:timers does, and the same Timeout object flows through both styles of code. The global proxy that used to intercept these calls is gone.

The

NodeJS.Timeout

type used above resolves out of the box, thanks to lib.node now being included by default.

Miscellaneous

A few smaller changes worth flagging that don’t fit neatly under any of the sections above:

  • The NODE_EXTRA_CA_CERTS environment variable is now honored at the root certificate store level (#33148), so extra CA certificates apply to every TLS code path: fetch(), Deno.connectTls(), node:https, and node:tls. Useful behind corporate MITM proxies or when you need to trust an internal CA across a whole script.
  • Linux release builds run again on systems with glibc older than 2.27 (#33259), restoring compatibility with a long tail of older distros and CI images.
  • deno compile configuration grows include and exclude fields, so you can declare bundled data files in deno.json instead of repeating --include flags on every invocation (#33024, docs).
  • Several additional Unix-style signals (SIGUSR1, SIGUSR2, and others) are now usable on Windows for Node compatibility (#32689).
  • deno x accepts a --package / -p flag for the case where the package name differs from the binary name (e.g. running tsc from npm:typescript) (#32855).
  • deno eval auto-detects CommonJS vs ES module syntax in the input snippet, so quick one-liners work without picking a flag (#32472).
  • Deno.upgradeWebSocket now wires up cleanly to node:http upgrade events, so Node-style servers can hand off WebSocket connections to Deno’s native implementation (#33342).
  • Deno.watchFs paths are normalized so events no longer carry leading ./ segments that broke string-equality checks against the watched paths (#33490).
  • FFI calls hold onto their argument backing stores for the duration of the call, preventing a use-after-free when async FFI work outlives the caller’s stack frame (#32775).
  • The event loop now wakes correctly when V8 posts foreground tasks from background threads, fixing a class of subtle hangs around async module evaluation (#32450).
  • deno_graph is bumped to 0.108.2 for proper handling of WebAssembly multi-value return types (#34070).
  • deno doc learns to render npm packages (#32435), with follow-up fixes for npm entrypoints that ship without types (#34147). The HTML output also picks up Prism highlighting for JSX/TSX code blocks (#33255) and cleaner operator rendering in dark mode (#33267).
  • --watch restarts now send SIGTERM and dispatch unload / process.exit before tearing the process down, so cleanup handlers actually run (#32564, #32664). The grace period before the hard kill drops from 5 seconds to 500 milliseconds (#33099), and --watch-exclude patterns are now respected for every file change event, not just the initial scan (#33854).
  • Text imports (with { type: "text" }) are stable and no longer require --unstable-raw-imports (#34238, introduced in 2.4). Bytes imports (with { type: "bytes" }) remain behind the flag. Learn more about import attributes.
  • deno publish no longer panics during provenance generation when run on CI providers other than GitHub (#33802).
  • Vite’s import.meta.hot references now type-check correctly (#32127).

V8 14.9

Deno 2.8 upgrades the V8 engine from 14.6 to 14.9 (#34226).

Acknowledgments

We couldn’t build Deno without the help of our community! Whether by answering questions in our community Discord server or reporting bugs, we are incredibly grateful for your support. In particular, we’d like to thank the following people for their contributions to Deno 2.8: Amol Yadav, Ashwin Naren, Avocet, BitToby, Dan Dascalescu, Daniel Osvaldo Rahmanto, Daniil Sivak, David Sherret, em, Felipe Cardozo, Fibi, Hajime-san, Hunnyboy1217, Janosh Riebesell, Jean Ibarz, Jimmy, John L. Carveth, Josh Fleming, kaju, Kenta Moriuchi, Kit Dallege, KnorpelSenf, KT, Kyle Kelley, Lach, Leo Zaki, lif, Luca Barbato, Luna, Marvin Hagemeister, Michael Horstmann, Nayeem Rahman, Nik B, Olivér Falvai, Pietro Marchini, r3wretrhy, Rano | Ranadeep, Rohan Santhosh Kumar, RoomWithOutRoof, Shivam Tiwari, tmimmanuel, Varun Chawla, and web-dev0521.

Would you like to join the ranks of Deno contributors? Check out our contribution docs here, and we’ll see you on the list next time.

Believe it or not, the changes listed above still don’t tell you everything that got better in 2.8. You can view the full list of pull requests merged in Deno 2.8 on GitHub.

That’s all for 2.8, thanks for reading and see you in the next release.

منتشرشده در

Deno Blog

خواندن در منبع

ادامه مطالعه