Skip to main content
Deno 1.39

Deno 1.39: The Return of WebGPU

Deno 1.39 marks a significant update in the Deno ecosystem, featuring the much-anticipated return of WebGPU, enhancing capabilities for graphics, gaming, and machine learning. We’ve also introduced new deno coverage reporters for improved codebase analytics and made substantial strides in Node.js compatibility, easing the transition for Node.js developers. Finally, this release includes updates to the Standard Library, performance optimizations, and the latest TypeScript 5.3 support.

If you already have Deno installed, upgrade to version 1.39 in your terminal with:

deno upgrade

If you don’t yet have Deno installed, you can install it with one of the following commands, or many other ways.

MacOS / Linux Install

curl -fsSL | sh

Windows Install

irm | iex

Here’s an overview of what’s new in Deno 1.39:

WebGPU is back

The WebGPU API gives developers a low level, high performance, cross architecture way to program GPU hardware from JavaScript. It is the effective successor to WebGL on the Web. The spec has been finalized and Chrome has already shipped the API. Support is underway in Firefox and Safari.

Deno first introduced WebGPU back in early 2021 but it was removed earlier this year due to performance problems. With this release, we are happy to re-introduce it with all the performance issues worked out.

GPUs have the ability to compute certain numerical operations with extreme parallelism as compared to CPUs. This is useful for a variety of applications, beyond just rendering and games. For example, machine learning algorithms can often be expressed as a series of matrix operations, which can be computed extremely efficiently on a GPU.

Our WebGPU implementation is based on the same underlying system as the upcoming WebGPU implementation in Firefox, so we’re confident that it’s a solid foundation for developers to build on.

A basic example of getting information about the GPU via WebGPU:

// Try to get an adapter from the user agent.
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
  // Print out some basic details about the adapter.
  const adapterInfo = await adapter.requestAdapterInfo();
  console.log(`Found adapter: ${adapterInfo.device}`); // On some systems this will be blank
  const features = [...adapter.features.values()];
  console.log(`Supported features: ${features.join(", ")}`);
} else {
  console.error("No adapter found");

Further examples can be viewed over at our webgpu-examples repository.

Although the specification is stable, WebGPU is still considered unstable in Deno. To access it in Deno use the --unstable-webgpu flag. We’re planning to stabilize it soon, after we’ve had a chance to get more feedback from the community and more time to verify the implementation against the specification test suite.

For more webgpu functionalities, we’ve also added std/webgpu (more info below).

Thanks to the wgpu team for the help provided to achieve this.

New deno coverage reporters

In this release, deno coverage received two new reporters: summary and html. You can now also omit the directory value for the --coverage flag in deno test, as we now default to using ./coverage/ directory.

The summary reporter is the new default reporter. It outputs the coverage summary in a concise table giving you information about coverage in particular files and overall summary:

$ deno coverage
File         | Branch % | Line % |
 bar.ts      |      0.0 |   57.1 |
 baz/quux.ts |      0.0 |   28.6 |
 baz/qux.ts  |    100.0 |  100.0 |
 foo.ts      |     50.0 |   76.9 |
 All files   |     40.0 |   61.0 |

The previously default reporter that prints uncovered lines in all files to the terminal is still available using the --detailed flag.

You can also specify --html flag to get the detailed coverage report in HTML format.

$ deno test --coverage
$ deno coverage --html
HTML coverage report has been generated at file:///path/to/project/coverage/html/index.html

The examples of the coverage report look like the below:

HTML Coverage Report Index

HTML Coverage Report Source

The output of deno coverage --html is completely static and can be hosted on any static file server, such as GitHub Pages.

We still support the --lcov flag, which outputs the coverage report in the LCOV format, which is useful for integrating with other tools such as Codecov or Coveralls.

We have further plans to improve deno coverage and make it more useful. If you have feedback you’d like to share, please comment on this issue.

Updates to deno compile

Deno 1.39 introduces significant advancements in the deno compile feature:

Better node_modules support: With the --unstable-byonm flag, deno compile now supports “bring your own node_modules”, enhancing Node.js compatibility. This feature allows you to use npm packages directly in your Deno projects, bridging the gap between Deno and the extensive npm ecosystem. Learn more about this feature in our previous blog post.

Flexible Naming for Executables: Deno has removed restrictions on executable naming. You can now name your compiled programs with leading digits, such as 3d_modeler, offering more flexibility in naming conventions.

More Dynamic Import Support: More dynamic import patterns are now supported in deno compile. This is interesting because Deno needs to statically include all modules that may be imported at runtime in the binary produced by deno compile. Because of this, dynamic imports can be problematic. Now, Deno can handle more dynamic import patterns, such as:

await import(`./dir/${expr}`);

In this example, Deno will include all modules from ./dir/ and its subdirectories into the compiled binary. This allows you to import any of these files at runtime. This update simplifies dependency management by ensuring that all modules referenced dynamically are available at runtime.

Enhanced Language Server

In our ongoing commitment to improving the Deno Language Server (LSP), this release introduces significant performance enhancements.

Responsive Typing Experience: We’ve optimized the handling of rapid request bursts during fast typing, ensuring a smoother and more responsive editing experience in your IDE.

Shutdown Timeout: To tackle the issue of lingering ‘zombie’ LSP instances, we’ve implemented a shutdown-timeout mechanism. This feature forcefully terminates LSP processes when you close your editor after a set timeout, promoting efficient resource utilization.

Update Notifications: The language server now actively informs you of the latest Deno updates. This feature aims to simplify the process of keeping your Deno installation current, ensuring you always have access to the latest features and fixes.

Enhanced Diagnostics: We’ve introduced a new deno.logFile setting in VSCode. When used alongside deno.internalDebug, this feature allows for the capture of detailed diagnostic data, aiding in performance analysis and troubleshooting.

We are dedicated to continuously enhancing the Deno LSP. Should you encounter any performance issues with the LSP or within VSCode Deno, your feedback is invaluable. Please let us know about your experience here.

Node.js compatibility improvements

Sloppy imports

Migrating existing TypeScript codebases to Deno can be a daunting task. One of the biggest hurdles is the fact that Deno requires you to explicitly specify file extensions in the import statements.

Imagine a TypeScript project that had a file foo.ts that imported bar.ts:

// foo.ts
import { Example } from "./bar";

// bar.ts
export const Example = "Example";

In previous versions of Deno, this would error out with an unhelpful message:

# Deno v1.38.5
$ deno run foo.ts
error: Module not found "file:///dev/bar"
  at file:///dev/foo.ts:1:25

Now in Deno 1.39 this error message is improved and an escape hatch made available:

# Deno v1.39.0
$ deno run foo.ts
error: Module not found "file:///dev/bar". Maybe add a '.ts' extension or run with --unstable-sloppy-imports
    at file:///dev/foo.ts:1:25

Running with the --unstable-sloppy-imports flag will execute the code without any changes:

# Deno v1.39.0
$ deno run --unstable-sloppy-imports foo.ts
Warning Sloppy imports are not recommended and have a negative impact on performance.
Warning Sloppy module resolution (hint: add .ts extension)
    at file:///dev/foo.ts:1:25

In addition to resolving imports without file extension, this feature also allows to resolve “directory” imports, as well as importing .ts files using .js extension:

// routes/index.ts
export default {
  "/": () => "Hello World",
  "/example": () => "Example",

// bar.ts
export const bar = "bar";

// foo.ts
import routes from "./routes";
import { bar } from "./bar.js";

$ deno run --unstable-sloppy-imports foo.ts
Warning Sloppy imports are not recommended and have a negative impact on performance.
Warning Sloppy module resolution (hint: specify path to index.ts file in directory instead)
    at file:///Users/ib/dev/deno/foo.ts:1:20
Warning Sloppy module resolution (hint: update .js extension to .ts)
    at file:///Users/ib/dev/deno/foo.ts:2:21
{ "/": [Function: /], "/example": [Function: /example] }

As seen above, using this flag will print warnings that guide you towards migrating the code to the recommended import syntax. You will also get diagnostics in your editor, along with “quick fixes” that will make it easier to apply necessary changes.

We hope this feature will make it much easier to try out and migrate existing projects to Deno.

Support running node_modules/.bin/ executables in deno task

deno task can run tasks defined in deno.json, as well as scripts defined in package.json. This release adds support for running executables in node_modules/.bin/ directory in deno task.

If you have package.json that looks like this:

  "scripts": {
    "dev": "vite dev"

You can run vite dev with deno task:

$ deno task dev

Deno will look for vite executable in node_modules/.bin/ directory and run it using Deno - even if the node_modules/.bin/vite uses shebang defining Node.js as an interpreter.

CommonJS entrypoints in node_modules

Deno will now correctly handle CommonJS entrypoints in node_modules, respecting the type setting from package.json for the relevant package. This should greatly improve compatibility with packages that still haven’t migrated to ESM.

Support for Object.prototype.__proto__

Deno made a conscious decision to not support Object.prototype.__proto__ for security reasons. However there are still many packages on npm that rely on this property to work correctly.

In this release we introduced a new --unstable-unsafe-proto flag that allows you to enable this property. It is not recommended to use this flag, but if you really need to use a package that relies on it, the escape hatch is now available to you.

Node.js APIs updates

Following Node.js APIs are now available:

  • crypto.createPrivateKey
  • http.ClientRequest.setTimeout
  • http.globalAgent
  • perf_hooks.performance
  • process.geteuid
  • util.parseArgs
  • vm.runInNewContext

Additionally we fixed several bugs in already supported Node.js APIs:

  • child_process.spawnSync handling of status was incorrect
  • child_process.spawnSync properly normalizes stdio
  • child_process can handle IPC pipes on Unix systems (Windows support coming soon)
  • crypto.sign now works with PEM private keys
  • fs.existsSync is now faster when file does not exist
  • process.exitCode should change exit code of process
  • allow null value for http.OutgoingMessage.setHeader
  • fix Buffer.copy when sourceStart > source.length
  • fix os.freemem
  • fix stream.Writable
  • handle closing process.stdin more than once

Changes to Deno APIs

This release includes several changes to the Deno APIs:

Deno.serve() support for Unix sockets is now stable

  { transport: "unix", address: "/tmp/my.sock" },
  (req) => new Response("Hello!"),

Deno.HttpServer.shutdown() is now stable

const server = Deno.serve((req) => new Response("Hello!"));

// Shutdown the server gracefully when the process is interrupted.
Deno.addSignalListener("SIGINT", () => {

await server.finished;

Deno.HttpClient can now be declared with using keyword

Following up on Explicit Resource Management introduced to Deno APIs in the previous release, this release brings support for using keyword to Deno.HttpClient:

  using client = Deno.createHttpClient({});
  const response = await fetch("http://localhost:4545/assets/fixture.json", {
  const json = await response.json();
// `client` is closed here

KV watch

The new, unstable API to watch for changes to the given keys in the given database. This API returns a ReadableStream that emits a new value whenever any of the watched keys change their versionstamp. The emitted value is an array Deno.KvEntryMaybeobjects, with the same length and order as thekeys array. Read more about the KV watch feature here.

const db = await Deno.openKv();

const stream =[["foo"], ["bar"]]);
for await (const entries of stream) {
  entries[0].key; // ["foo"]
  entries[0].value; // "bar"
  entries[0].versionstamp; // "00000000000000010000"
  entries[1].key; // ["bar"]
  entries[1].value; // null
  entries[1].versionstamp; // null


The Deno.cron function, introduced recently, has seen an exciting update in this release. It now supports an intuitive JSON format for defining schedules, making it easier to understand and implement cron jobs. For more in-depth information, explore our detailed blog post about the cron feature.

Traditional Unix Cron Format

Previously, setting up a task to run every 20 minutes required the Unix cron format:

Deno.cron("myCron", "*/20 * * * *", () => {
  console.log("Running every 20 minutes");

New JSON Format

Now, you can achieve the same with a more readable JSON format:

Deno.cron("myCron", {
  minutes: { every: 20 },
}, () => {
  console.log("Running every 20 minutes");

Deprecated IO interfaces

We’ve been pushing Deno APIs to use Web Streams API for multiple months now and to finalize this change the v1.39 release brings a deprecation to the various IO interfaces in Deno.

The following interfaces are now deprecated and are slated to be removed in Deno 2:

  • Deno.Reader
  • Deno.ReaderSync
  • Deno.Writer
  • Deno.WriterSync
  • Deno.Closer

You will now get a warning in your editor when using these interfaces, with suggestions to use Web Streams APIs instead.

Changes to Web APIs


This API allows for listening to multiple AbortSignals, and will abort if any of the specified signals is aborted.

This can be useful if you have an API that provides an AbortSignal (for example, Request), and you want to additionally depend on another AbortSignal.


The ImageData Web API is a class that can be used to represent an image in a standardized format.

A quick example of creating a 1x2 pixels red image:

const rawImage = new Uint8ClampedArray([255, 0, 0, 1, 255, 0, 0, 1]);
new ImageData(rawImage, 1, 2);

Thanks to @jamsinclair who contributed this feature. min option

The min option for allows for specifying the minimum amount of bytes to read. This is useful for various encoding formats that require reading an n amount of bytes, which can be achieved by setting the min option to the same length as the byte length of the buffer.

For example:

const response = await fetch("");
const reader = response.body!.getReader({ mode: "byob" });
const buffer = new Uint8Array(10);
// value will contain at least 5 bytes written to it
const { value } = await, { min: 5 });

Improved URLPattern performance

The URLPattern API has received performance improvements that make it 6 to 8 times faster when matching against multiple patterns, this is an optimization directed towards a popular scenario of using URLPattern in an HTTP router.

Standard Library updates


Alongside releasing WebGPU in the CLI, we also added std/webgpu in this release.

This module adds some additional functionalities, including createTextureWithData for creating a GPUTexture with existing data, describeTextureFormat to get information about a texture format, and createCapture to take a “capture” using a texture, among others.

Some of these functionalities have been ported/based on wgpu features, and further functionalities are planned.


In this release std/expect has been added. The module exports two functions: expect and fn. These are designed to be compatible with Jest.

import { expect } from "";

expect(Math.max(5, 8)).not.toEqual(5);
expect(Math.max(5, 8)).toEqual(8);

expect(Math.pow(3, 5)).toBeGreaterThan(100);
expect(Math.pow(3, 5)).toBeLessThan(300);

Currently expect supports the following matcher and modifier APIs: not, resolves, rejects, toBe, toEqual, toStrictEqual, toMatch, toMatchObject, toBeDefined, toBeUndefined, toBeNull, toBeNaN, toBeTruthy, toBeFalsy, toContain, toContainEqual, toHaveLength, toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeLessThanOrEqual, toBeCloseTo, toBeInstanceOf, toThrow, toHaveProperty, toHaveLength.

The module also provides mock related util and assertions:

import { expect, fn } from "";

const mockFn = fn();



The following mock related matchers are implemented: toHaveBeenCalled, toHaveBeenCalledTimes, toHaveBeenCalledWith, toHaveBeenLastCalledWith, toHaveBeenNthCalledWith, toHaveReturned, toHaveReturnedTimes, toHaveReturnedWith, toHaveLastReturnedWith, toHaveNthReturnedWith.

The module is not completely compatible with expect API of Jest. The following APIs are not implemented yet: toMatchSnapShot, toMatchInlineSnapShot, toThrowErrorMatchingSnapShot, toThrowErrorMatchingInlineSnapShot, expect.anything, expect.any, expect.arrayContaining, expect.not.arrayContaining, expect.closedTo, expect.objectContaining, expect.not.objectContaining, expect.stringContaining, expect.not.stringContaining, expect.stringMatching, expect.not.stringMatching, expect.assertions, expect.hasAssertions, expect.addEqualityTester, expect.addSnapshotSerializer, expect.extend.

Standard Library also has std/testing/bdd module. Now you can write your test cases in BDD style using Standard Library.

import { describe, it } from "";
import { expect } from "";

describe("Math", () => {
  describe("max", () => {
    it("returns max value from the given args", () => {
      expect(Math.max(1, 2, 3)).toEqual(3);
      expect(Math.max(-3, -2, -1)).toEqual(-1);

Save this file as my_test.ts and you can execute it with the command:

$ deno test my_test.ts
Check file:///path/to/my_test.ts
running 1 test from ./my_test.ts
Math ...
  max ...
    returns max value from the given args ... ok (1ms)
  max ... ok (1ms)
Math ... ok (1ms)

ok | 1 passed (2 steps) | 0 failed (1ms)

Thanks Thomas Cruveilher for contributing to this feature.


In this release std/ini has been added. The module provides the parser and serializer for working with INI files.

Thanks Aaron Huggins for contributing to this feature.


std/data_structures has been added in this release. This module exports classes that implements data structure algorithms. Currently RedBlackTree, BinarySearchTree, and BinaryHeap classes are available.


std/text has been added in this release. This module exports utility functions for comparing/sorting/choosing texts with certain criteria:

import {
} from "";

// Calculates levenshtein distance of 2 words
console.log(levenshteinDistance("hello", "halo")); // => prints 2
console.log(levenshteinDistance("hello", "world")); // => prints 4

console.log(closestString("puhs", ["commit", "pull", "push"])); // => prints "push"

Thanks Jeff Hykin for contributing these features.


std/cli has been added in this release. This module currently exports 2 APIs: parseArgs and promptSecret.

import {
} from "";

const parsedArgs = parseArgs(["--foo", "--bar=baz", "./quux.txt"]);
// prints: { foo: true, bar: "baz", _: ["./quux.txt"] }
// This is typically used as `parseArgs(Deno.args)`

const credential = promptSecret("Enter the credential ");
// This asks for the credential in the terminal and the user inputs are masked with '*'

Note: parseArgs is the same as parse in std/flags, but we realized the scope of std/flags is too limited, and decided to explore more CLI-related features under std/cli.

Thanks Alisue for proposing and implementing promptSecret.


std/net has been added in this release. This module exports 1 API: getAvailablePort, which is useful for choosing available TCP port.

import { getAvailablePort } from "";

const port = await getAvailablePort();

Thanks Harry Solovay for contributing to this feature.

TypeScript 5.3

Deno v1.39 ships with latest release of TypeScript 5.3, read more in Announcing TypeScript 5.3 blog post.

Upcoming changes to the decorators

Currently, Deno enables experimental TypeScript decorators by default. Due to the TC39 Decorator proposal being at Stage 3 and a popular request we’ve decided to change the default setting for decorators in the upcoming Deno v1.40 release.

In the next release the experimental TypeScript decorators will be disabled by default and the TC39 Decorators will be enabled by default.

If you are using experimental TS decorators, prepare yourself for this change by adding this configuration to your deno.json:

  "compilerOptions": {
    "experimentalDecorators": true

Thank you to our contributors!

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 1.39: Aravind, Birk Skyum, Bolat Azamat, Chen Su, Daniel Mizerski, Florian Schwalm, Gasman, Ian Bull, Jacob Hummer, Jakub Jirutka, Jamie, Jesse Jackson, John Spurlock, Jordan Harband, Julien Cayzac, Jérôme Benoit, Kenta Moriuchi, Laurence Rowe, Max Goodhart, Raashid Anwar, Tareque Md Hanif, btoo, citrusmunch, lionel-rowe, liruifengv, pk, scarf, ud2, and 林炳权.

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 1.39. You can view the full list of pull requests merged in Deno 1.39 on GitHub here.

Thank you for catching up with our 1.39 release, and we hope you love building with Deno!

🍋 Fresh 1.6 is out.

Fresh v1.6 apps have expanded plugin APIs, faster route matching, and official Tailwind CSS support.