Deno 2.0 Release Candidate
UPDATE 2024/10/04: We’ve released several updates to the Deno 2.0 Release Candidate.
We’ve been planning a new major version of Deno for years. Many times, it seemed
imminent, but we realized the features we wanted required more work. Now, it’s
finally happening. Last month, we released the final 1.x version with
1.46, and today, we’re cutting the release candidate for Deno
2.0, which includes everything we expect in the final release. This is the
largest update since 1.0, with major changes like the introduction of Node’s
process
global. We’ve also made some philosophical shifts, like preferring
deno install
over the now-deprecated deno cache
. Read on for the full list
of changes and
share your feedback!
To try out the release candidate, run these instructions in your terminal:
deno upgrade
deno upgrade rc
ℹ️ If you are using alternative distribution method like Homebrew,
deno upgrade
might not be available.Follow the installation instructions at https://deno.com, then run the aforementioned commands.
What’s new in Deno 2.0 Release Candidate
- Changes to global variables
window
andprocess
- Dependency management
- Permission system changes
- API changes
- CLI changes
- Import assertions are dead, long live import attributes
- Node.js and npm compatibility
- Doc tests with
deno test --doc
- TypeScript changes
- Acknowledgements
- What’s next?
🚨️ We’re actively seeking feedback 🚨️
This release candidate helps us identify issues prior to the final 2 release. If you encounter any issues or need extra guidance, please create an issue in GitHub or ask in our Discord’s
#deno-2-help
channel. Our team is actively monitoring both places and will help out as soon as we can.
Changes to global variables
Deno 2 comes with two major changes to global variables — window
is gone and
Node’s process
is now available.
We introduced the window
global in Deno v1.0, with the goal being to make Deno
as browser-compatible as possible. Unfortunately, the window
global variable
became a source of problems for users.
Many libraries check if they are executed in the browser by probing for a
window
global variable instead of checking for the existence of DOM. This led
to a class of bugs in libraries that would otherwise work in Deno, due to window
being globally available.
Deno started to
discourage use of window
global in v1.40
suggesting to use globalThis
or self
instead.
// Deno v1.x
window.addEventListener("load", () => {
console.log("loaded");
});
// Deno v2.x
globalThis.addEventListener("load", () => {
console.log("loaded");
});
In contrast, the process
global has been widely requested.
While it has been possible to use process
by importing it from node:process
module for a long time, many popular frameworks rely on its presence in the
global scope, often used in configuration files.
Although adding import process from 'node:process';
seems simple, it often
causes friction for users of popular frameworks that would otherwise work
seamlessly in Deno.
So with addition of process
global, you can expect a lot more code written
originally for Node.js to work with no changes in Deno. However, we still
encourage users to prefer explicit imports. Thus a new no-process-global
lint
rule was added that will provide hints and quick-fixes in your editor to use an
import statement instead.
Dependency management
Deno 2 comes with several new features that improve dependency management.
The deno add
subcommand now handles specifiers with a subpath:
# Before in Deno v1.46
deno add jsr:@std/testing/snapshot
error: Failed to parse package required: @std/testing/snapshot
Caused by:
0: Invalid package requirement '@std/testing/snapshot'. Invalid version requirement. Invalid specifier version requirement. Unexpected character '/'
...
# Deno v2.0
deno add jsr:@std/testing/snapshot
Add jsr:@std/testing@1.0.2
# Deno v1.46
deno add npm:preact/hooks
error: Failed to parse package required: npm:preact/hooks
Caused by:
0: Invalid package requirement 'preact/hooks'. Packages in the format <scope>/<name> must start with an '@' symbol.
1: Packages in the format <scope>/<name> must start with an '@' symbol.
# Deno v2.0
deno add npm:preact/hooks
Add npm:preact@10.24.0
ℹ️ Using
jsr:
ornpm:
prefixes is now required when adding dependencies to avoid potential ambiguity between packages with same names in both registries.If you omit the prefix Deno will print a suggestion with correct invocation, checking which registries contain the package.
Additionally, if your project contains package.json
file, Deno will prefer
adding npm:
dependencies to package.json
, rather than deno.json
.
cat package.json
{
"dependencies": {}
}
deno add npm:express
Add npm:express@5.0.0
cat package.json
{
"dependencies": { "express": "^5.0.0" }
}
You can also add “dev dependencies” to package.json
using the --dev
flag:
deno add --dev npm:express
Add npm:express@5.0.0
cat package.json
{
"devDependencies": { "express": "^5.0.0" }
}
deno install
now supports the --entrypoint
flag, which allows you to install
all dependencies from a given module(s):
// main.ts
import snapshot from "jsr:@std/testing/snapshot";
import express from "npm:express";
deno install --entrypoint main.ts
Download ...
A new deno remove
subcommand has been added to quickly remove some of the
dependencies:
deno add jsr:@std/testing
Added jsr:@std/testing@1.0.2
cat deno.json
{
"imports": { "@std/testing": "jsr:@std/testing@^1.0.2" }
}
deno remove @std/testing
Removed @std/testing
cat deno.json
{}
You can also use deno remove
to handle dependencies listed in package.json
.
Deno 2 ships with a new, more concise lockfile format (v4), that will minimize diffs when updating dependencies and ensure reproducible builds.
cat deno.lock
{
"version": "4",
"specifiers": {
"jsr:@std/assert@^1.0.4": "1.0.5",
"jsr:@std/data-structures@^1.0.2": "1.0.4",
"jsr:@std/fs@^1.0.3": "1.0.3",
"jsr:@std/internal@^1.0.3": "1.0.3",
"jsr:@std/path@^1.0.4": "1.0.6",
"jsr:@std/testing@*": "1.0.2",
"jsr:@std/testing@^1.0.2": "1.0.2"
},
// ...
}
Deno will automatically migrate you to the new lockfile format.
Finally, Deno has improved its error messaging, providing helpful hints for common issues like incorrectly formatted relative import paths, or missing dependencies when using “bare specifiers”:
// main.ts
import "@std/dotenv/load";
deno run main.ts
error: Relative import path "@std/dotenv/load" not prefixed with / or ./ or ../
hint: Try running `deno add jsr:@std/dotenv/load`
at file:///main.ts:1:8
These updates collectively streamline the process of managing dependencies in Deno projects, making it more intuitive and aligned with modern development workflows.
Permission system changes
Deno.errors.NotCapable
error
New Deno’s permission system is one of its most loved features. When a program tries
to access an API that was not allowed using --allow-*
flags an error is
raised. In Deno v1.x, this was Deno.errors.PermissionDenied
.
There was one problem with that though. All operating systems raise this error
too, for example when your user does not have access to an admin-only file, or
when you are trying to listen on a privledged port. Because of this, users were
often confused to see that error raised despite running with --allow-all
flag.
In Deno v2.0, a lack of Deno permissions now raises the Deno.errors.NotCapable
error instead to make it easier to discriminate between OS-level errors and Deno
errors.
await Deno.readTextFile("./README.md");
Deno v1.46
$ deno run main.ts
error: Uncaught (in promise) PermissionDenied: Requires read access to "./README.md", run again with the --allow-read flag
await Deno.readTextFile("./README.md")
^
at Object.readTextFile (ext:deno_fs/30_fs.js:878:24)
at file:///main.ts:1:12
Deno v2.0
$ deno run main.ts
error: Uncaught (in promise) NotCapable: Requires read access to "./README.md", run again with the --allow-read flag
await Deno.readTextFile("./README.md")
^
at Object.readTextFile (ext:deno_fs/30_fs.js:777:24)
at file:///main.ts:1:12
Deno.mainModule
doesn’t require --allow-read
permission
Permissions check for Deno.mainModule
API, which gives you a full path of the
main module, has been relaxed and no longer requires full --allow-read
permission. This also applies to process.argv
API.
This requirement is deprecated, but the path to the main module can be obtained
by creating an Error
instance and inspecting its stack trace.
--allow-hrtime
flag is gone
Deno v1.x had an --allow-hrtime
flag that affected APIs like
performance.now()
providing high resolution timing. In Deno 2, this flag is
gone and these APIs always provide high resolution timing.
This flag was deprecated as high-resolution timing can be achieved using
standard JavaScript APIs like Worker
and SharedArrayBuffer
.
--allow-run
flag changes
There are a few big changes for --allow-run
flag to guide you towards safer
execution of subprocesses.
First, a warning will appear if you use the --allow-run
flag without
specifying an allow list of binary names or paths to binaries:
new Deno.Command("echo", { args: ["hello"] }).spawn();
$ deno run --allow-run main.ts
Warning --allow-run without an allow list is susceptible to exploits. Prefer specifying an allow list (https://docs.deno.com/runtime/fundamentals/security/#running-subprocesses)
hello
Then, anytime a subprocess is spawned with LD_*
or DYLD_*
environmental
variables, a full --allow-run
permission will be required. Note that these
environmental variables should rarely be relied on. If you really need to use
them, you should consider further sandboxing Deno by running additional layers
of protection (e.g. a Docker container).
new Deno.Command("echo", {
env: {
"LD_PRELOAD": "./libpreload.so",
},
}).spawn();
$ deno run --allow-run=echo main.ts
error: Uncaught (in promise) NotCapable: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
}).spawn();
^
at spawnChildInner (ext:runtime/40_process.js:182:17)
at spawnChild (ext:runtime/40_process.js:205:10)
at Command.spawn (ext:runtime/40_process.js:479:12)
at file:///main.ts:5:4
Escape commas in file names
It is now possible to grant permissions for reading and writing files that contain commas in the file name.
In Deno v1.x, a comma was used to delimit between multiple file names:
deno run --allow-read=file1.txt,file2.txt
# grants permission to read `file1.txt` and `file2.txt`
making it impossible to grant permission to a file that contains a comma in its name.
In Deno 2 this can be done by escaping a comma with another comma:
deno run --allow-read=file,,.txt,file2.txt
# grants permission to read `file,.txt` and `file2.txt`
API changes
Stable APIs
Several APIs have been stablized in this Deno 2:
WebGPU
APIs no longer require--unstable-webgpu
flagDeno.dlopen()
and other FFI APIs no longer require--unstable-ffi
flagDeno.createHttpClient()
no longer requires--unstable-http
flag
The error messages have been improved as well, providing useful hints when you try to use an unstable API without a corresponding flag:
const db = await Deno.openKv();
Deno v1.46
$ deno run db.ts
error: Uncaught (in promise) TypeError: Deno.openKv is not a function
const db = await Deno.openKv();
^
at file:///db.ts:1:23
Deno v2.0
error: Uncaught (in promise) TypeError: Deno.openKv is not a function
const db = await Deno.openKv();
^
at file:///db.ts:1:23
info: Deno.openKv() is an unstable API.
hint: Run again with `--unstable-kv` flag to enable this API.
Deno
APIs
Breaking changes to ℹ️ For more information how to migrate away from the deprecated APIs visit the Deno 1 to 2 Migration Guide
Following APIs have been removed:
Deno.Buffer
Deno.close()
Deno.copy()
Deno.customInspect
Deno.fdatasync()
andDeno.fdatasyncSync()
Deno.File
(additionallyDeno.FsFile
can’t be constructed manually anymore)Deno.flock()
andDeno.flockSync()
Deno.fstat()
andDeno.fstatSync()
Deno.fsync()
andDeno.fsyncSync()
Deno.ftruncate()
andDeno.ftruncateSync()
Deno.funlock()
andDeno.funlockSync()
Deno.futime()
andDeno.futimeSync()
Deno.iter()
andDeno.iterSync()
Deno.metrics()
Deno.read()
andDeno.readSync()
Deno.readAll()
andDeno.readAllSync()
Deno.resources()
Deno.seek()
andDeno.seekSync()
Deno.shutdown()
Deno.write()
andDeno.writeSync()
Deno.writeAll()
andDeno.writeAllSync()
Handling of TLS options was updated, and following options are no longer supported:
Deno.ConnectTlsOptions.certChain
Deno.ConnectTlsOptions.certFile
Deno.ConnectTlsOptions.privateKey
Deno.ListenTlsOptions.certChain
Deno.ListenTlsOptions.certFile
Deno.ListenTlsOptions.keyFile
Following interfaces have been removed:
Deno.Closer
Deno.Reader
andDeno.ReaderSync
Deno.Seeker
andDeno.SeekerSync
Deno.Writer
andDeno.WriterSync
Additionally several interfaces related to DNS record handling have been renamed:
Deno.CAARecord
toDeno.CaaRecord
Deno.MXRecord
toDeno.MxRecord
Deno.NAPTRRecord
toDeno.NaptrRecord
Deno.SOARecord
toDeno.SoaRecord
Deno.SRVRecord
toDeno.SrvRecord
The “resource IDs” are no longer available in following APIs:
Deno.FsWatcher.prototype.rid
Deno.Conn.prototype.rid
Deno.TlsConn.prototype.rid
Deno.TcpConn.prototype.rid
Deno.UnixConn.prototype.rid
Deno.FsFile.prototype.rid
Deno.Listener.prototype.rid
Deno.TlsListener.prototype.rid
Finally, a few APIs have been “soft-deprecated”. These APIs will keep working, but will no longer receive updates or bug fixes. It is highly recommended and encouraged to migrate to stable counterparts:
Deno.serveHttp()
- useDeno.serve()
insteadDeno.run()
- usenew Deno.Command()
insteadDeno.isatty(Deno.stdin.rid)
- useDeno.stdin.isTerminal()
insteadDeno.isatty(Deno.stdout.rid)
- useDeno.stdout.isTerminal()
insteadDeno.isatty(Deno.stderr.rid)
- useDeno.stderr.isTerminal()
instead
Command Line Interface changes
Deno 2 removes support for two subcommands:
deno bundle
- this subcommand was deprecated in Deno v1.31 due to mismatch in expectations of users and the actual functionality provided by the built-in bundler.Many Deno users expected a general-purpose, highly customizable bundler. However Deno’s built-in bundler was meant as a simple tool to concatenate multiple files into a single-file for easier distribution; there were not settings to customize any behavior either.
We plan to implement a new built-in bundler, so look out for updates in future releases.
deno vendor
- was deprecated in Deno v1.45 and superseded by a much easier solution of usingvendor
option indeno.json
file introduced in Deno v1.37
Several CLI flags are now deprecated:
--lock-write
- use--frozen
instead--unstable
- use granular--unstable-<feature>
flags insteadtest --allow-none
- usetest --permit-no-files
--jobs
- useDENO_JOBS
env var insteadtest --trace-ops
- usetest --trace-leaks
--ts
- use--ext=ts
instead
Additionally, you can enable debug logging by using DENO_LOG
environmental
variable, instead of RUST_LOG
.
Lastly, the files
options in the config file are now deprecated.
In Deno v1.x this syntax was supported:
{
"test": {
"files": {
"include": ["**/*.ts"],
"exclude": ["ignore.ts"]
}
}
}
In Deno 2, the files
configuration has been simplified, being flattened into
the parent configuration:
{
"test": {
"include": ["**/*.ts"],
"exclude": ["ignore.ts"]
}
}
Import assertions are dead, long live import attributes
Import Assertions support was deprecated in Deno v1.46 and is no longer available in Deno 2.
The reason being, the proposal has undergone major changes, including updating
the keyword from assert
to with
, and being renamed to
Import Attributes,
additionally some browsers (eg. Chrome) had already unshipped support for Import
Assertions too.
Here’s what the update looks like:
- import data from "./data.json" assert { type: "json" };
+ import data from "./data.json" with { type: "json" };
Node.js and npm compatibility
Improved CommonJS support
Since its 1.0 release, Deno has prioritized ES modules as the primary module
system, offering only limited support for CommonJS through manual creation of
require
:
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
require("...");
While we firmly believe that ES modules are the future of JavaScript, the landscape in 2024 still includes numerous libraries and projects relying on CommonJS.
Despite Deno’s robust handling of CJS libraries over the past year, users occasionally faced challenges when integrating CommonJS, particularly within their own codebases.
In Deno 2, several improvements were made to help working with CommonJS modules and make transition to ES modules easier:
deno run index.cjs
- Deno can now execute CommonJS files, provided that they use.cjs
extension. Deno does not look forpackage.json
files andtype
option to determine if the file is CommonJS or ESM.When using this option, Deno will also not automatically install dependencies, so you will need to manually run
deno install
upfront to ensure all required dependencies are available.
// index.cjs
const express = require("express");
NOTE: Deno permissions system is still in effect, so require()
calls will
prompt for --allow-read
permissions.
deno run index.cjs
┏ ⚠️ Deno requests read access to "/dev/example".
┠─ Learn more at: https://docs.deno.com/go/--allow-read
┠─ Run again with --allow-read to bypass this prompt.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >
import cjs from "./index.cjs"
- Deno can now import CommonJS files, provided that they use.cjs
extension. Deno does not look forpackage.json
files andtype
option to determine if the file is CommonJS or ESM.
// greet.cjs
module.exports = {
hello: "world",
};
import greet from "./greet.cjs";
console.log(greet);
deno run main.js
{
"hello": "world"
}
require(ESM)
- Deno now supportsrequire
ing ES modules. This is only possible if the modules required do not use Top-Level Await. This change should improve interoperability when using mixed dependencies that rely on CommonJS and ESM.
// greet.js
export function greet(name) {
return `Hello ${name}`;
}
// esm.js
import { greet } from "./foo.js";
export { greet };
// main.cjs
const esm = require("./esm");
console.log(esm);
console.log(esm.greet("Deno"));
deno run --allow-read main.cjs
[Module: null prototype] { greet: [Function: greet] }
Hello Deno
- Better suggestions for errors when dealing with CommonJS modules, that guide you towards a working program:
// main.js
module.exports = {
foo: "foo",
};
deno run main.js
error: Uncaught (in promise) ReferenceError: module is not defined
module.exports = {
^
at file:///Users/ib/dev/deno/main.js:1:1
info: Deno does not support CommonJS modules without `.cjs` extension.
hint: Rewrite this module to ESM or change the file extension to `.cjs`.
node_modules
is the default
Bring your own The “bring your own node modules” functionality was introduced in Deno v1.38.
ℹ️ While we still strongly advocate to not rely on local
node_modules
directory, a lot of existing projects and frameworks operate on the assumption that this directory will be present.
But sometimes you still want to have a local node_modules
directory even if
you don’t have a package.json
- eg. when using frameworks like Next.js, Remix
or Svelte; or when depending on npm packages that use Node-API like duckdb
,
sqlite3
, esbuild
and others. To improve compatibility, if a project contains
package.json
file, Deno will expect that node_modules
directory will be set
up manually.
In previous Deno release you could use --node-modules-dir
flag or
nodeModulesDir
option in the config file to tell Deno if the node_modules
directory should be created or not.
Deno 2 changes this configuration option - it’s no longer a boolean, but rather an enum with three options:
The default mode
deno run main.ts
# or
deno run --node-modules-dir=none main.ts
or with a configuration file
{
"nodeModulesDir": "none"
}
This is the default mode for Deno-first projects - ie. projects that do not have
package.json
file. It automatically installs dependencies into the global
cache and doesn’t create a local node_modules
directory.
ℹ️ Recommended for new projects. Note that frameworks that expect a
node_modules
directory will not work, in addition to any npm dependencies that rely onpostinstall
scripts.
auto
mode
The deno run --node-modules-dir=auto main.ts
or with a configuration file
{
"nodeModulesDir": "auto"
}
The auto
mode automatically install dependencies into the global cache and
creates a local node_modules
directory in the project root.
ℹ️ Recommended for projects that have npm dependencies that rely on
node_modules
directory - mostly projects using bundlers or ones that have npm dependencies withpostinstall
scripts.
manual
mode
The deno run --node-modules-dir=manual main.ts
or with a configuration file
{
"nodeModulesDir": "manual"
}
The manual
mode is the default mode for projects using package.json
. This
mode is the workflow that Node.js uses and requires an explicit installation
step using deno install
/npm install
/pnpm install
or any other package
manager.
ℹ️ Recommended for projects using frameworks like Next.js, Remix, Svelte, Qwik, etc; or tools like Vite, Parcel, Rollup.
It is recommended to use the default none
mode, and fallback to auto
or
manual
mode if you get errors complaining about missing packages inside
node_modules
directory.
Node.js APIs compatibility
Once again, a great progress was made in terms of supported built-in Node.js APIs, resulting in even more popular packages being supported:
node:crypto
received numerous updates:- supports MD4 digests fixing
npm:docusaurus
Cipheriv.update(string, undefined)
now works correctly- So does
Decipheriv
whenautoPadding
option is disabled - exporting JWK public key is now supported…
- as is importing EC JWK keys…
- and importing JWK octet key pairs…
- and importing RSA JWK keys…
- finally,
npm:web-push
is supported
- supports MD4 digests fixing
async_hooks.AsyncLocalStorage
is now more performant and reliablenpm:detect-port
andnpm:portfinder
are now supportednpm:ssh2
gets better support- So does
npm:elastic-apm-node
node:wasi
is now stubbednode:events
andnode:util
now have better coveragenpm:puppeteer
is more reliable thanks to addition ofFileHandle#writeFile
API and better handling of socket upgradesnode:constants
,node:fs
,node:path
node:vm
andnode:zlib
export missing constantsnode:trace_events
is now stubbed and doesn’t crashnode:timers
now exportspromises
symbolnpm:qwik
andnpm:hono
are more reliable thanks to fixes tohttp2
modulenpm:playwright
is more stable thanks to support fordetached
option inchild_process
v8
module gets better support forSerializer
andDeserializer
APIs makingnpm:parcel
work- and many more…
deno test --doc
Doc tests with JSDoc is a format for writing inline documentation for
JavaScript and TypeScript. It’s how
deno doc
and
JSR automatically generate documentation
for its modules.
JSDoc allows us to write code examples directly in comments:
/**
* ```ts
* import { assertEquals } from "jsr:@std/assert/equals";
*
* assertEquals(add(1, 2), 3);
* ```
*/
export function add(a: number, b: number) {
return a + b;
}
There’s one problem though… How do you ensure that these examples stay up to date and don’t code-rot?
Deno v1.10 added feature to type-check the examples, which helped the situation, but did not solve it completely. The only way to ensure the examples are up to date is to actually execute them.
In Deno 2, deno test --doc
not only type-checks the examples in JSDoc, but
also executes them:
To test your JSDoc examples, simply run deno test --doc
and Deno will
automatically discover and run relevant codeblocks.
ℹ️ If you want to only type-check your examples, you can still do so by running
deno check --doc
.
But that’s not all! In addition to testing JSDoc example, you can also execute codeblocks in Markdown files:
This is the first iteration of this feature and we’d appreciate any feedback.
TypeScript changes
Deno v2.0 ships with TypeScript 5.6, you can read about it more in Announcing TypeScript 5.6 blog post.
To help catch common pitfalls, these TypeScript settings are now enabled by default:
Additionally, Deno 2 ships with built-in support for @types/node
at version 22
to make it easier to type-check code relying on Node.js APIs.
Finally, to ensure your compilerOptions
settings are up-to-date, we’ve made it
an allow list, so Deno will complain and flag it if you use an unsupported
option.
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 the Deno 2 release candidate: Andreas Deininger, Armaan Salam, Bedis Nbiba, Birk Skyum, Bob Callaway, Caleb Cox, Caleb Lloyd, Coty, Hajime-san, HasanAlrimawi, Ian Bull, Ivancing, Jake Abed, Kamil Ogórek, Kenta Moriuchi, Kyle Kelley, Mohammad Sulaiman, MrEconomical, MujahedSafaa, Pig Fang, Rano | Ranadeep, Roy Ivy III, Sean McArthur, Sʜɪᴍᴜʀᴀ Yū, Victor Turansky, Yazan AbdAl-Rahman, Zebreus, chirsz, cions, i-api, melbourne2991, seb, vwh, and Łukasz Czerniawski.
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.
Thank you for catching up with our Deno 2 release candidate, and we hope you love building with Deno!
What’s next?
This release candidate is working towards our 2.0 release and it should be
expected that there will be bugs and issues. If you encounter any, please let us
know in GitHub issues (with the 2.0
tag) or on
our Discord’s dedicated #deno-2-help
channel. We’re
actively monitoring both areas to ensure that all major bugs are fixed prior to
our 2.0 release.