Skip to main content
Deno 1.38

The Deno ecosystem continues to mature, and with the 1.38 release, we’re excited to introduce significant improvements to the deno doc command. Topping the list is the ability to produce static site documentation using the new deno doc --html feature, a game-changer for developers looking to share and publish their project documentation.

If you already have Deno installed, upgrade to version 1.38 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.38.

deno doc updates

Drawing heavy inspiration from the acclaimed rustdoc, we are proud to introduce significant enhancements to the deno doc command in this release.

At the forefront is the new deno doc --html command. This enables you to generate a static documentation site for your project, making it easier than ever to share insights with your team or broadcast them on the web.

$ deno doc --html --name="My library" ./mod.ts
Written 11 files to ./docs/

The resulting site offers a comprehensive index of your library and dedicated pages for each exported symbol. Enhancing the user experience is a client-side symbol search, reminiscent of rustdoc’s indexed search. But don’t worry about dependencies; the site remains functional even with JavaScript disabled.

For a real-world glimpse of this output, explore the generated documentation for the standard library’s fs module.

For those eager to contribute, check out the curated list of issues to help elevate the documentation experience for the Deno community.

Furthermore, deno doc has expanded its horizons. Gone are the days of restricting documentation generation to a single module. With this release, pass multiple entrypoints and watch as deno doc crafts documentation for each seamlessly.

$ deno doc ./mod.ts other.ts

Note, though, the slight alteration in filtering on the command line. To specify a symbol, you’ll now need to use the --filter flag.

Documentation generation has become more intelligent in this release as well. With the power of a newly implemented symbol graph in Rust, it now comprehends intricate relations between types and symbols. For instance, it now recognizes when a private interface is used by an exported type, tailoring its output accordingly.

The new --lint flag makes it easier to find potential problems and opportunities in your documentation. As you generate documentation, it scans for issues, pointing out potential pitfalls and suggesting remedies. Whether it’s an error due to an unexported type, a missing return type, or a missing JS doc comment on a public type, deno doc --lint has you covered, ensuring your documentation is of the highest caliber.

For instance, if you were to omit the : string return type in a sample, the output might look something like:

$ deno doc --lint mod.ts
Type 'getName' references type 'Person' which is not exported from a root module.
Missing JS documentation comment.
Missing return type.
    at file:///mod.ts:8:1

error: Found 3 documentation diagnostics.

With these checks in place, deno doc not only helps you craft stellar documentation but also optimizes type-checking in your projects.

Hot module replacement

Deno v1.38 ships with hot module replacement. Hot Module Replacement (HMR) is a feature in JavaScript development that allows you to update and replace modules in an application without requiring a full page refresh or a complete application restart. It’s a valuable tool for improving the development workflow.

You can try it out using deno run --unstable-hmr mod.ts:

This flag works exactly like --watch flag, but instead of restarting the process it tries to patch the files in-place. Similarly to --watch you can pass additional files that should be watched for changes:

$ deno run --unstable-hmr=data.json ./mod.ts

Keep in mind that all files passed to the flag will stil cause a full process restart; also in some situations it’s not possible to patch the file in-place (eg. if you changing a top-level symbol or an async function that is currently running). In such cases the process will also do a full restart.

This feature is very useful in various frameworks settings - you might have an application that has both backend and frontend written in Deno, then it’s very useful to be able to notify the browser that a file has been changed and the browser should reload the page. To help in scenarios like that, you can programatically listen for "hmr" events and act accordingly:

addEventListener("hmr", (e) => {
  // `e` is a `CustomEvent` instance and contains `path` property in its
  // `detail` field.
  console.log("HMR triggered", e.detail.path);

Node.js compatibility improvements

To make it easier to try Deno with a Node.js project, this release introduces unstable support for using the npm package manager of your choice with Deno.

To try it out, execute the following commands:

$ mkdir example && cd example
$ npm init -y
$ npm install cowsay

Now create a deno.json to enable the --unstable-byonm flag for our project:

  "unstable": ["byonm"]

And a simple main.ts file:

import cowsay from "cowsay";

  text: `Hello from Deno using BYONM!``,

Now run the project:

$ deno run main.ts
✅ Granted read access to "node_modules/cowsay/cows/default.cow".
< Hello from Deno using BYONM! >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

If we go and delete the node_modules directory, you’ll see an error telling us to run npm install first:

$ rm -rf node_modules
$ deno run main.ts
error: Could not resolve "cowsay", but found it in a package.json. Deno expects the node_modules/ directory to be up to date. Did you forget to run `npm install`?
    at file:///example/main.ts:1:20

Of course, we can also use pnpm:

$ pnpm install
$ deno run --allow-read=. main.ts
< Hello from Deno using BYONM! >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

This feature greatly increases the compatibility of Deno with the npm ecosystem. In order to make Deno work out of the box similarly to Node with no additional setup instructions, we are considering making this the default when a package.json exists in an upcoming release. This would be a breaking change–the current auto-install behavior would be opt-in instead of opt-out. See this issue comment for more details and please share your thoughts there.

Note that if you are using this feature in an editor, you will have to restart the Deno Language Server after running npm install or pnpm install in order for the new dependencies to be picked up. We will fix this in a future release. Follow #21042 for updates.

If you want to know more about Node.js compatibility in Deno, check out our Node.js compatibility list.

Bare specifiers for Node.js built-ins

In Deno, using Node.js built-ins requires a node: scheme:

import fs from "node:fs";

This works in both Node.js and Deno, but Deno errors if you only specify "fs":

> deno run main.ts
error: Relative import path "fs" not prefixed with / or ./ or ../
If you want to use a built-in Node module, add a "node:" prefix (ex. "node:fs").
    at file:///main.ts:1:16

Starting in this release, you can avoid this error by using the --unstable-bare-node-builtins flag (or "unstable": ["bare-node-builtins"] in a deno.json). Alternatively, this also works with --unstable-byonm. That said, it’s still recommended to add a node: scheme to these specifiers.

Node.js APIs updates

A bunch of fixes to Node.js APIs landed in this release:

  • buffer.Buffer.utf8ToBytes
  • crypto.randomFillSync
  • http2.ClientHttp2Session
  • os.availableParallelism
  • process.argv0
  • process.version now returns 18.18.0 version for node
  • tty.ReadStream
  • tty.WriteStream
  • uv.errname

Fast(est) JSX transform

This release introduces a new JSX transform that is optimized for server-side rendering. It works by serializing the HTML parts of a JSX template into static string arrays at compile time, instead of creating hundreds of short lived objects.

Traditionally, JSX leaned quite heavily on the Garbage Collector because of its heritage as a templating engine for in-browser rendering.

// Input
const a = <div className="foo" tabIndex={-1}>hello<span /></div>

// Output
import { jsx } from "react/jsx-runtime";

const a = jsx(
    className: "foo",
    tabIndex: -1,
    children: [
      jsx("span", null)

Every element expands to two object allocations, a number which grows very quickly the more elements you have.

const a = {
  type: "div",
  props: {
    className: "foo",
    tabIndex: -1,
    children: [
        type: "span",
        props: null,

The new transform moves all that work to transpilation time and pre-serializes all the HTML bits of the JSX template.

// input
const a = (
  <div className="greeting">
    <a href={link}>
      Hello <b>{name}!</b>

// output
import { jsxAttr, jsxEscape, jsxTemplate } from "preact/jsx-runtime";

const tpl = ['<div class="greeting"><a ', ">hello <b>", "!</b></a></div>"];
const a = jsxTemplate(tpl, jsxAttr("href", link), jsxEscape(name));

The jsxTemplate function can be thought of as a tagged template literal. It has very similar semantics around concatenating an array of static strings with dynamic content. To note here is that the className has been automatically transformed to class, same for tabIndex transformed to tabindex. Instead of creating thousands of short lived objects, the jsxTemplate function essentially just calls Array.join() conceptually. Only the output string is an allocation, which makes this very fast.

In our testing we observed around 7-20x faster rendering times and a 50% reduction in Garbage Collection times. The more HTML parts the JSX has, the greater the speedup. For components we’ll fall back to the traditional JSX transform internally.

The transform was intentionally designed in a way that makes it independent of any framework. It’s not tied to Preact or Fresh either. By setting a custom jsxImportSource config in deno.json you can point it to your own runtime module.

  "compilerOptions": {
    "jsx": "precompile",
    "jsxImportSource": "custom"
  "imports": {
    "custom/jsx-runtime": "path/to/my/mod.ts"

The custom jsx runtime is expected to export the following functions:

  • jsxTemplate(strings, ...dynamic)
  • jsxAttr(name, value)
  • jsxEscape(value)
  • jsx(type, props, key)

deno run --env

A new unstable --env flag can now be used to specify a .env file to load (ex. or to load the .env file in the current working directory if no value is provided (ex. --env). This has the advantage of not needing --allow-read=.env permissions to load the .env file.

$ cat .env
$ cat main.js
$ deno run --env main.js
✅ Granted env access to "MY_ENV_VAR".

We’ll be working on improving this in the coming releases to add features such as multi-line variable support.

WebSockets improvements

Numerous bugs in were fixed in the WebSockets implementation, making sending of large packets over secure streams much more reliable and fixing occasional lockups in long-running WebSocket streams. WebSocket clients are also using the new rustls-tokio-stream crate which is our new TLS implementation that we’ll be rolling out slowly in Deno.

Deno also now supports RFC8441 WebSockets over http/2, which is supported for servers that advertise only http/2 support, and support the http/2 extended connect protocol with the websocket protocol. To match the behaviour of browsers, any server that supports both http/1.1 and http/2 WebSockets will continue to use http/1.1.

deno task supports head command

The deno task command now supports the head command cross-platform, which allows you to view the first few lines of a file.

  "tasks": {
    "readmeSummary": "head -n 3"
$ deno task readmeSummary
# Deno

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that

The head accepts a single file and supports -n/--name argument to define number of lines to print. By default it prints 10 lines.

Thank you @dahlia for contributing this feature!

VSCode extension and language server

Support for VSCode’s built-in TS/JS options

Some of the Deno language server’s configuration options are re-implementations of options for VSCode’s typescript-language-features. For example:

  "deno.inlayHints.parameterNames.enabled": "all"

These can now be toggled under their original keys. Here is the full list of supported options and their defaults:

  "javascript.inlayHints.enumMemberValues.enabled": false,
  "typescript.inlayHints.enumMemberValues.enabled": false,
  "javascript.inlayHints.functionLikeReturnTypes.enabled": false,
  "typescript.inlayHints.functionLikeReturnTypes.enabled": false,
  "javascript.inlayHints.parameterNames.enabled": "none",
  "typescript.inlayHints.parameterNames.enabled": "none",
  "javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
  "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true,
  "javascript.inlayHints.parameterTypes.enabled": false,
  "typescript.inlayHints.parameterTypes.enabled": false,
  "javascript.inlayHints.propertyDeclarationTypes.enabled": false,
  "typescript.inlayHints.propertyDeclarationTypes.enabled": false,
  "javascript.inlayHints.variableTypes.enabled": false,
  "typescript.inlayHints.variableTypes.enabled": false,
  "javascript.inlayHints.variableTypes.suppressWhenTypeMatchesName": true,
  "typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName": true,
  "javascript.preferences.autoImportFileExcludePatterns": [],
  "typescript.preferences.autoImportFileExcludePatterns": [],
  "javascript.preferences.importModuleSpecifier": "shortest",
  "typescript.preferences.importModuleSpecifier": "shortest",
  "javascript.preferences.jsxAttributeCompletionStyle": "auto",
  "typescript.preferences.jsxAttributeCompletionStyle": "auto",
  "javascript.preferences.quoteStyle": "auto",
  "typescript.preferences.quoteStyle": "auto",
  "javascript.preferences.useAliasesForRenames": true,
  "typescript.preferences.useAliasesForRenames": true,
  "javascript.suggest.autoImports": true,
  "typescript.suggest.autoImports": true,
  "javascript.suggest.classMemberSnippets.enabled": true,
  "typescript.suggest.classMemberSnippets.enabled": true,
  "javascript.suggest.completeFunctionCalls": false,
  "typescript.suggest.completeFunctionCalls": false,
  "javascript.suggest.enabled": true,
  "typescript.suggest.enabled": true,
  "javascript.suggest.includeAutomaticOptionalChainCompletions": true,
  "typescript.suggest.includeAutomaticOptionalChainCompletions": true,
  "javascript.suggest.includeCompletionsForImportStatements": true,
  "typescript.suggest.includeCompletionsForImportStatements": true,
  "javascript.suggest.names": true,
  "typescript.suggest.objectLiteralMethodSnippets.enabled": true,
  "javascript.suggest.paths": true,
  "typescript.suggest.paths": true,
  "javascript.updateImportsOnFileMove.enabled": "prompt",
  "typescript.updateImportsOnFileMove.enabled": "prompt"

Deno tasks side bar view

Similar to VSCode’s built-in NPM scripts explorer, you can now explore ‘tasks’ specified by your deno.json in a side bar view.

Create a deno.json which includes task definitions:

  "tasks": {
    "run": "deno run --allow-net --allow-read=. main.ts",
    "dev": "deno run --watch --allow-net --allow-read=. main.ts"

Deno tasks view demonstration

Thank you @ArmaanAS for contributing this feature!

Cache-all-dependencies quick fix

Diagnostics for uncached dependencies had been accompanied by a quick fix option to cache that dependency. Now there is an option to cache all uncached dependencies of the containing module.

Cache-all-dependencies quick fix demonstration

Other fixes and enhancements

  • Show ‘related information’ when hovering TS diagnostics.
  • Show diagnostics for untitled files.
  • Resolve remote import maps.
  • Don’t commit registry import completions when inputting /.
  • Refresh content on repeated invocations of the Deno: Language Server Status command.
  • Show diagnostics for type imports from untyped dependencies.
  • Invalidate old diagnostics when a deleted file is recreated.
  • Fix bugs related to viewing remote modules in the editor.
  • Allow formatting files in a vendor/ directory.
  • Initial support for Jupyter notebooks in the language server.
  • Various performance improvements.

Deno.test support in the REPL

This release brings JSX and Deno.test support to the REPL. You can copy-paste existing tests that will be run directly in the REPL:

$ deno
> Deno.test("foo", () => {
  if (1 !== 2) {
    throw new Error("1 !== 2");

foo ... FAILED (3ms)


foo => <anonymous>:1:27
error: Error: 1 !== 2
    at <anonymous>:3:11


foo => <anonymous>:1:27

FAILED | 0 passed | 1 failed (0ms)

Similarly you can copy-paste existing JSX components to use them in the REPL.

$ deno
> /** @jsxImportSource */
> const test = <h1>Test</h1>
> test
  type: "h1",
  props: { children: "Test" },
  key: undefined,
  ref: undefined,
  __k: null,
  __: null,
  __b: 0,
  __e: null,
  __d: undefined,
  __c: null,
  __h: null,
  constructor: undefined,
  __v: -1,
  __source: undefined,
  __self: undefined

This feature enabled JSX usage in Jupyter notebooks as well!

Jupyter Notebook updates

Two new APIs were added to the Deno.jupyter namespace:

  • Deno.jupyter.display
  • Deno.jupyter.broadcast

You can now use these APIs to display rich content and communicate with the frontend in Jupyter notebooks:

await Deno.jupyter.display({
  "text/plain": "Hello, world!",
  "text/html": "<h1>Hello, world!</h1>",
  "text/markdown": "# Hello, world!",
}, { raw: true });
await Deno.jupyter.broadcast("comm_msg", {
  comm_id: comm_id,
  data: {
    method: "update",
    state: { value: 50, a: null, b: null },
    buffer_paths: [["a"], ["b"]],
}, {
  buffers: [
    new TextEncoder().encode("Yay"),
    new TextEncoder().encode("It works"),

Thank you to Kyle Kelley and Trevor Manz for implementing these features.

Deno API changes


The Deno.serve API received a support for Unix domain sockets:

import { assertEquals } from "";

const socketPath = "/tmp/path/to/socket.tmp";
const server = Deno.serve(
    path: socketPath,
  (_req, { remoteAddr }) => {
    assertEquals(remoteAddr, { path: filePath, transport: "unix" });
    return new Response("hello world!");

Additionally, Deno.serve now returns a Deno.HttpServer instead of Deno.Server. The old Deno.Server type is now deprecated and will be removed in the future.

using with Deno APIs

TypeScript 5.2 upgrade which shipped in Deno v1.37 introduced using keyword that adds support for the Explicit Resource Management proposal. This proposal introduces a convenient way to handle resources performing async actions. This release adds support for disposable resources for the following Deno APIs:

  • Deno.Command
  • Deno.FsFile
  • Deno.FsWatcher
  • Deno.HttpConn
  • Deno.HttpServer
  • Deno.Kv
  • Deno.Listener

You can now use using keyword to automatically close the resource when they go out of scope:

let kv2: Deno.Kv;

  using kv = await Deno.openKv(":memory:");
  kv2 = kv;

  const res = await kv.get(["a"]);
  assertEquals(res.versionstamp, null);
  // `kv` gets closed here...

// it's not usable anymore.
await assertRejects(() => kv2.get(["a"]), Deno.errors.BadResource);

Please note that using keyword is currently only supported in TypeScript files.

Web API changes

For compatibility we added which is an empty string. In the future we might consider setting this value to change the process name.


The EventSource Web API is a client for interacting with server-sent events, which is a one-way persistent connection for sending events.

Here is a simple example for a server, and then using the EventSource as a client. The server sends two events, and the client logs these two events and then on the second event closes the connection.

// server.ts
import {
} from "";

Deno.serve({ port: 8000 }, (_) => {
  const target = new ServerSentEventStreamTarget();
    new ServerSentEvent("message", {
      data: "hello",
      id: 1,
    new ServerSentEvent("message", {
      data: "world",
      id: 2,
  return target.asResponse();
// client.ts
const source = new EventSource("http://localhost:8000");

source.onopen = () => {

source.onmessage = (e) => {
  console.log(`Data for id '${e.lastEventId}':`,;

  if (e.lastEventId == 2) {

Standard Library updates

Updates to std/path

2 platform-specific submodules have been added in this release. You can now import operations for posix paths from std/path/posix and ones for windows from std/path/windows. Both submodules also have single API export paths, such as std/path/posix/basename.ts, which were unavailable before this release.

Because of the above changes, the old methods of using platform specific APIs have been deprecated. If you are using the following patterns, please update thoses imports to the new specifiers.

// Old patterns, which are now deprecated
import { posix, win32 } from "";
import * as posix from "";
import * as win32 from "";

// New patterns
import * as posix from "";
import * as windows from "";

Updates to std/http

http/server has been deprecated in this release. Please use Deno.serve instead.

http_errors, cookie_map, server_sent_event, method are now prefixed with unstable_. These modules will be redesigned or removed in the future.

Updates to std/encoding

encode and decode methods for hex, base64, base64url, base32, base58, ascii85 are deprecated now. Instead these modules now exports encodeHex, decodeHex, encodeBase64, decodeBase64, etc. The users are encouraged to update the existing usages to the new APIs.

This change has been made because the existing encode and decode methods have several different API signatures, such as encode(input: Uint8Array): Uint8Array, encode(input: Uint8Array): string, encode(input: ArrayBuffer | string): string. These subtle differences are hard to remember and confusing. The new APIs always have encode(input: string | Uint8Array | ArrayBuffer): string for encoding and decode(input: string): Uint8Array for decoding.

Deprecation of std/io

std/io module has been deprecated to discourage the use of legacy Reader/Writer interfaces. Most I/O operations are accessible through ReadableStream-based APIs now, which are the recommended way to perform I/O in Deno.

Deprecation of std/wasi

std/wasi module has been deprecated due to low usage and minimal feedback from the community. If you still need to use WASI in Deno, use wasmer-js instead.

Granular --unstable-* flags

You may have noticed the introduction of flags prefixed with –unstable- in the details above. In this release, we have implemented a granular feature flagging system to enhance the way we introduce new features. Previously, to activate new features, the general –unstable flag was necessary. This flag, however, was rather generic–it activated all unstable features without giving users the option to select specific ones.

With this update, we offer a suite of flags that enable users to turn on specific unstable features:

  • --unstable-bare-node-builtins
  • --unstable-broadcast-channel
  • --unstable-byonm
  • --unstable-cron
  • --unstable-ffi
  • --unstable-fs
  • --unstable-hmr
  • --unstable-http
  • --unstable-kv
  • --unstable-net
  • --unstable-worker-options

If you want to use only the FFI API, you don’t have to enable all the other APIs, instead you can run like so:

$ deno run --allow-read --unstable-ffi main.ts

Additionally, you can now employ the deno.json configuration file to determine which features you’d like to activate by using the"unstable" config key:

  "unstable": ["byonm", "fs", "kv"]

The above config file would enable the new “Bring your own node_modules” feature, unstable file system APIs as well as Deno KV API.

While the --unstable flag – which turns on all unstable features– remains functional, we plan to start issuing deprecation warnings in the subsequent release and will recommend the use of the more precise flags.

Performance improvements

This release marks the end of our migration to our new Rust/v8 fast op system, #[op2]. Every operation in Deno that interacts with the outside world (fetch, Deno.serve, Kv, and many more) runs through the op infrastructure, and the new version of the op system will allow us to continue tuning performance, especially for async APIs.

We can now adjust the behavior of every type of parameter that we send between Rust and V8 to extract the maximum performance. We hope to blog more about the op interface later in the future. Keep an eye on the blog for more!

In addition to the new op system, we’ve also made a number of other performance improvements:

  • deno test should be even faster to startup thanks to the improvements in test registration and stack trace collection

  • DOMException is now much cheaper to create

  • structuredClone is now much faster when transferables are not used

V8 12.0

This release ships with V8 12.0 which adds two exciting JavaScript features:

As an example, Array.fromAsync plays well with Deno KV:

> await Array.fromAsync(kv.list({ prefix: [] }))
    key: [ "foo" ],
    value: "bar",
    versionstamp: "00000000000000040000"

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.38: Alessandro Scandone, Chen Su, Hirotaka Tagawa / wafuwafu13, Jared Flatow, Jesper van den Ende, Jérôme Benoit, Kenta Moriuchi, Kyle Kelley, Laurence Rowe, Marcos Casagrande, Mark A. Hershberger, Mikhail, Mikko, Rui He, Trevor Manz, sigmaSd, 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.38. You can view the full list of pull requests merged in Deno 1.38 on GitHub here.

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

🍋 Did you know? Fresh got even fresher.

Fresh v1.5 apps are snappier and more responsive, thanks to client-side navigation with Partials, ahead-of-time tranpsilation, and more.