Deno 1.21 Release Notes
Deno 1.21 has been tagged and released with the following new features and changes:
deno check
globalThis.reportError
and the"error"
event- Improvements to the Deno language server and VSCode extension
- Improvements to the REPL
DENO_NO_PROMPT
environmental variable- Improvements to unstable APIs
- Incremental formatting and linting
- Improvements to
deno bench
- New unstable API for subprocesses
- Improvements to
deno test
If you already have Deno installed, you can upgrade to 1.21 by running:
deno upgrade
If you are installing Deno for the first time, you can use one of the methods listed below:
# Using Shell (macOS and Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh
# Using PowerShell (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
# Using Homebrew (macOS):
brew install deno
# Using Scoop (Windows):
scoop install deno
# Using Chocolatey (Windows):
choco install deno
deno check
, and the path to not type checking by default
The last few years have shown how tremendously useful it is to be able to annotate JavaScript code with type information (usually in the form of TypeScript). This has two primary benefits:
- Type information helps generate documentation automatically, both in the form of IDE hints / completions, and in the form of static documentation pages like on https://doc.deno.land.
- Type information can be used to verify that a given bit of code is semantically correct (type check).
Both of these benefits are very useful, but at different times. The first is an ambient benefit that is always useful during development. The second is a more sophisticated benefit that is only useful just before you are going to ship some code. The type checker is really just a very very powerful linter that can help you discover problems in your code before you run it.
Up to now, deno run
has always automatically performed type checking on the
code it was about to run, just before running it. This can sometimes be a nice
user experience, but more often than not it is not what you want. The reason for
this is often that type checking is really slow: it is often by far the single
largest factor impacting the startup performance of your application.
The thing is that most developers use an IDE that surfaces the results of the
type check at development time already. All users using deno lsp
get this
experience out of the box. This means that when they run their freshly developed
code, they have to wait a long time for a type check to be performed even though
it is not actually useful, because they can already see all diagnostics in their
IDE.
Additionally, with JavaScript being on the path to get type annotations natively, the semantics browsers will use when they encounter type comments will be unlike Deno. They will not type check before running some code, instead leaving that step to a separate type checking step that developers run before shipping the code.
In line with this, we have made the decision to start Deno on the path of
disabling type checking by default in deno run
. Type checking will need to be
performed explicitly using the new deno check
subcommand. Because we know that
this change is rather invasive at this time, we are going to take it slow. Our
tentative plan for the timeline to make this change is as follows:
- This release adds the new
deno check
subcommand, and aDENO_FUTURE_CHECK=1
environment variable that can be set to switch Deno into the “new” no-typecheck-by-default mode that will be default in the future. - We will spend a couple of releases doing outreach and informing users of the change.
- In an upcoming release in a couple of months, we will make the switch to
disable type checking by default on
deno run
.
Disabling type checking by default on deno run
does not mean we are
removing TypeScript support from Deno. TypeScript is still a first class
language in Deno, and we will continue to encourage users to use TypeScript for
their projects. The only change that we are making is that users will now have
to explicitly specify when they want to perform type checking: either by running
deno check
, or by specifying the --check
option on deno run
.
The new deno check
subcommand also has slightly different default type
checking semantics compared to the existing type checking in deno run
: it only
reports diagnostics for the current project (like deno lint
or deno fmt
) and
not any diagnostics for remote dependencies. This behaviour is already available
in deno run
through the --no-check=remote
flag. Type checking of the entire
module graph is still possible by running deno check --remote
.
We know this change may have been unexpected by some users, but in reality we have been wanting to make this change for nearly a year. We think this change significantly improves developer experience for users of Deno, and we think that we can align ourselves better to what the JavaScript community as a whole has done in the past, and will do in the future.
If you have feedback on this change, or the planned timeline, please hop onto our Discord server to discuss.
globalThis.reportError
and the "error"
event
This release aligns Deno’s error handling behaviour for uncaught exceptions in
asynchronous event loop tasks like setTimeout
, setInterval
, or event
handlers to the browser. Deno now has a global "error"
event that will be
dispatched for any uncaught exceptions in the above mentioned APIs. Users can
event.preventDefault()
this event to prevent the runtime from exiting with a
non 0 status code like it usually would on uncaught exceptions.
Additionally, the web standard globalThis.reportError
has been added to let
users report errors in the same way that uncaught exceptions in asynchronous
event loop tasks would. globalThis.reportError(error)
is different to
setTimeout(() => { throw error }, 0)
in that the former synchronously performs
the exception reporting steps, while the latter performs them in a future event
loop tick (asynchronously).
Here is an example of the new feature:
// This code will cause Deno to print
// the exception to stderr and will exit with a non 0 status code.
reportError(new Error("something went wrong!"));
// But if a user handles the "error" event, they can prevent termination and log
// the error themselves:
window.onerror = (e) => {
e.preventDefault();
console.error("We have trapped an uncaught exception:", e);
};
reportError(new Error("something went wrong!"));
Thank you Nayeem Rahman for contributing this feature!
Improvements to the Deno language server and VSCode extension
deno task
integration
Autodiscovery of config file and Deno’s VSCode extension will now prompt you to enable it in a workspace if
deno.json
or deno.jsonc
files are discovered.
Additionally,
tasks defined in the tasks
section from the config file
will be available in the command palette:
Enable extension in subpaths of the workspace
This feature adds a long awaited ability to enable Deno extension only in some
parts of the configured workspace by using Deno: Enable Paths or
"deno.enablePaths"
settings.
For example if you have a project like this:
project
├── worker
└── front_end
Where you only want to enabled the worker
path (and its subpaths) to be Deno
enabled, you will want to add ./worker
to the list of Deno: Enable Paths in
the configuration.
Testing API integration
vscode_deno
now provides integration with VSCode’s Testing API, making it
possible to run your Deno test from the UI:
Make sure to update to latest version of Deno VSCode extension to be able to use these features.
Improvements to the REPL
The REPL is a tool for quick prototyping and trying new things and there’s little use to perform type-checking, especially for imported, third party code. To this end, we decided to disable type-checking for imported modules in the REPL, leading to faster imports.
A new feature in this release, is the --eval-file
flag that can be used with
the deno repl
subcommand. This flag allows you to pass a list of paths or URLs
to files, that will be executed before the REPL starts. This feature is useful
for creating custom, specialized REPLs.
$ deno repl --eval-file=https://deno.land/[email protected]/encoding/ascii85.ts
Download https://deno.land/[email protected]/encoding/ascii85.ts
Deno 1.21.0
exit using ctrl+d or close()
> rfc1924 // local (not exported) variable defined in ascii85.ts
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
Keep in mind, that files provided via the --eval-file
flag, are executed in
the same scope as the REPL itself. That means that all files are executed as
“plain, old scripts”—not ES modules—and they all share the same global scope.
One consequence of this, is that evaluated files may need to use absolute
specifiers in import statements, as relative specifiers will be resolved
relative to the current working directory of the REPL.
See more details in the manual entry
Thank you Naju Mancheril for contributing this feature!
In addition, the REPL now has a global clear()
function available that acts as
an alias for console.clear()
. This aligns with what’s found in the REPL of
many browsers.
DENO_NO_PROMPT
environmental variable
This release adds a new DENO_NO_PROMPT
environment variable. When it is set,
deno
will disable all interactive prompts, even when the output is an
interactive terminal. It has an identical effect to if one would specify
--no-prompt
on all invocations to the deno
binary.
Improvements to unstable APIs
Deno.upgradeHttp
Unix socket support for The unstable Deno.upgradeHttp
API that can be used to perform HTTP protocol
switches now supports protocol switches on HTTP servers running on top of unix
connections.
.unref()
for Deno.Listener
The Deno.Listener
API now has new .ref()
and .unref()
methods that can be
called to enable or disable operations on this listener from blocking the event
loop. For example, if a user creates a new listener by calling Deno.listen()
,
and then calls .accept()
on the listener, the process will not exit until a
connection has been accepted.
If the user calls .unref()
on the listener after creation, the .accept()
task would no longer block the process from exiting. The event loop would
complete once all other asynchronous tasks have been completed, ignoring the
completion state of the .accept()
task of the listener.
Incremental formatting and linting
deno fmt
and deno lint
now use a behind the scenes cache in order to skip
files it already knows are formatted or linted from previous runs. These
subcommands are already very fast, but with this change you should see a
noticeable performance improvement after you’ve run them at least once on some
files, especially when working on a slow computer with little parallelism.
For example, here is how long deno fmt
and deno lint
took to run on
deno_std
’s repo on a computer with 4 cores (2.60GHz) before this change:
$ time deno fmt
Checked 927 files
1.796s
$ time deno fmt
Checked 927 files
1.799s
$ time deno lint
Checked 872 files
2.201s
$ time deno lint
Checked 872 files
2.209
Now after:
$ time deno fmt
Checked 927 files
1.764s
$ time deno fmt
Checked 927 files
0.114s
$ time deno lint
Checked 872 files
2.292s
$ time deno lint
Checked 872 files
0.106s
Notice that the time goes down significantly after the first run of each command.
Additionally, starting with this release, deno fmt
will automatically skip
formatting files inside .git
directories.
deno bench
Improvements to In Deno v1.20,
we introduced a new deno bench
subcommand,
that allows to quickly register and run bits of code to assess their
performance. This subcommand was modelled after deno test
and had a similar
output format.
We received great feedback about this feature from the community. Two complaints were especially vocal:
- default number of iterations was too low in most cases
- report format had too little information
Given that we marked Deno.bench()
as an unstable API, we took the opportunity
to address both complaints and add more features to the Deno.bench()
API.
Deno.BenchDefinition.n
and Deno.BenchDefinition.warmup
that specified how
many times each case should be run are now removed - instead the benchmarking
tool will run a bench case repeatedly until the time difference between
subsequent runs is statistically insignificant (this is similar to Golangs
approach).
Deno.BenchDefinition.group
and Deno.BenchDefinition.baseline
were added,
these fields allow you to neatly group related bench cases and mark one of them
as the basis for comparison for other cases.
// This is the baseline case. All other cases belonging to the same "url"
// group will be compared against it.
Deno.bench({ name: "Parse URL", group: "url", baseline: true }, () => {
new URL(import.meta.url);
});
Deno.bench({ name: "Resolve URL", group: "url" }, () => {
new URL("./foo.js", import.meta.url);
});
Lastly, the bench report was reworked to include more useful information, including a comparison between cases from the same groups.
Thank you @evanwashere for contributing this feature.
New unstable API for subprocesses
Deno 1.21 adds new unstable subprocess APIs to the Deno namespace that
incorporates much of the feedback we have received from the community about the
(soon-to-be deprecated) Deno.run
subprocess API.
High level API
A new easy-to-use API has been added to spawn a subprocess and collect its
output in a single call. The Deno.spawn
API takes an options bag similar to
the options Deno.run
takes, but instead of returning a Deno.Process
object,
it returns a Promise
that resolves to a Deno.SpawnOutput
. This contains the
exit status of the process, and a Uint8Array
s containing the bytes the process
outputted to stdout
and stderr
.
const { status, stdout, stderr } = await Deno.spawn(Deno.execPath(), {
args: [
"eval",
"console.log('hello'); console.error('world')",
],
});
console.assert(status.code === 0);
console.assert("hello\n" === new TextDecoder().decode(stdout));
console.assert("world\n" === new TextDecoder().decode(stderr));
Low-level API
A new low-level API has also been added (Deno.spawnChild
). It works very
similar to Deno.run
but has some notable differences:
All stdio streams are now
ReadableStream
/WritableStream
instead ofDeno.Reader
/Deno.Writer
.Instead of calling
proc.status()
to get a promise resolving to the exit status of the process, you can now get the status promise from thechild.status
getter.The subprocess no longer has to be manually closed with
.close()
, like inDeno.run
.A new
child.output()
API has been added that has the same return value asDeno.spawn()
.
const child = Deno.spawnChild(Deno.execPath(), {
args: [
"eval",
"console.log('Hello World')",
],
stdin: "piped",
});
// open a file and pipe the subprocess output to it.
child.stdout.pipeTo(Deno.openSync("output").writable);
// manually close stdin
child.stdin.close();
const status = await child.status;
Synchronous subprocess execution
Synchronous subprocess execution is a new capability that was not previously
possible with Deno. This new Deno.spawnSync
API has a signature that is nearly
identical to Deno.spawn
. The only difference is that the return type is
SpawnOutput
rather than Promise<SpawnOutput>
.
const { status, stdout, stderr } = Deno.spawnSync(Deno.execPath(), {
args: [
"eval",
"console.log('hello'); console.error('world')",
],
});
console.assert(status.code === 0);
console.assert("hello\n" === new TextDecoder().decode(stdout));
console.assert("world\n" === new TextDecoder().decode(stderr));
The new API is unstable. We encourage you to try it out though, and report any feedback you have.
deno test
Improvements to This release brings a lot of improvements and new features to Deno’s built-in testing functionalities.
User code output formatting
Previously, deno test
was not really aware of any output that might come from
user code. That situation often led to interleaved output between test runner’s
report and console logs coming from your code. In this release we’ve reworked a
lot of plumbing in the test runner, which allows us to be mindful of any output
coming from your code, either from console
API methods, or direct writes to
Deno.stdout
and Deno.stderr
.
Starting in this release, if there is output coming from your code, it will be neatly enclosed between markers to make it distinct from the test runner’s report.
We are also considering capturing this output by default and only showing it, if the test fails. We’d love to hear your feedback, please let us know in the issue.
More informative errors and stack traces
deno test
always provided full stack traces for thrown errors, however this is
not desirable if your test files don’t have a deep call stack. The result is
that you’d see a lot of stack frames coming from internal Deno code, that don’t
help you pinpoint where something goes wrong:
// test.ts
Deno.test("error in a test", () => {
throw new Error("boom!");
});
$ deno test test.ts
Check file:///dev/deno/test.ts
running 1 test from file:///dev/deno/test.ts
test error in a test ... FAILED (3ms)
failures:
error in a test
Error: boom!
at file:///dev/deno/test.ts:2:9
at testStepSanitizer (deno:runtime/js/40_testing.js:444:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:145:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:370:13)
at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:427:15)
at runTest (deno:runtime/js/40_testing.js:788:18)
at Object.runTests (deno:runtime/js/40_testing.js:986:28)
at [deno:cli/tools/test.rs:512:6]:1:21
failures:
error in a test
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (9ms)
error: Test failed
As can be seen above, the error contains 8 stack traces, but only the top frame contains useful information to debug the problem.
Starting in this release, deno test
will filter out stack frames that are
coming from Deno’s internal code and show the line of code where the error
originates:
$ deno test test.ts
Check file:///dev/deno/test.ts
running 1 test from ./test.ts
error in a test ... FAILED (5ms)
failures:
./test.ts > error in a test
Error: boom!
throw new Error("boom!");
^
at file:///dev/deno/test.ts:2:9
failures:
./test.ts
error in a test
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (15ms)
error: Test failed
Thank you Nayeem Rahman for contributing this feature!
We intend to further improve the test reporter in the coming releases to provide the best testing experience.
BDD style testing
We added a BDD (or Behavior Driven Development) style test runner to Deno Standard Modules in this release.
There are 2 major styles of organizing test cases in the JavaScript testing
ecosystem. One is the test
function style
(tap,
tape, and
ava belong to this group), and the other is
BDD (jasmine, mocha,
jest, and vitest belong to this
group) style (or describe
-and-it
function style).
While we chose a test
function style for our default testing API
(Deno.test()
) for its simplicity, there has always been a need from the
community for the BDD style syntax. Due to this need, we decided to add support
for this style in this release, and the feature is now available in
std/testing/bdd.ts
.
The basic usage of describe
and it
functions looks like the following:
import {
assertEquals,
assertStrictEquals,
assertThrows,
} from "https://deno.land/[email protected]/testing/asserts.ts";
import {
afterEach,
beforeEach,
describe,
it,
} from "https://deno.land/[email protected]/testing/bdd.ts";
import { User } from "https://deno.land/[email protected]/testing/bdd_examples/user.ts";
describe("User", () => {
it("constructor", () => {
const user = new User("John");
assertEquals(user.name, "John");
assertStrictEquals(User.users.get("John"), user);
User.users.clear();
});
describe("age", () => {
let user: User;
beforeEach(() => {
user = new User("John");
});
afterEach(() => {
User.users.clear();
});
it("getAge", function () {
assertThrows(() => user.getAge(), Error, "Age unknown");
user.age = 18;
assertEquals(user.getAge(), 18);
});
it("setAge", function () {
user.setAge(18);
assertEquals(user.getAge(), 18);
});
});
});
describe
and it
functions wrap the Deno.test
API internally, so you can
execute the above test with the deno test
command as usual.
$ deno test bdd_example.ts
running 1 test from ./bdd.ts
User ...
constructor ... ok (4ms)
age ...
getAge ... ok (3ms)
setAge ... ok (3ms)
ok (10ms)
ok (18ms)
test result: ok. 1 passed (4 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (40ms)
In addition to describe
and it
, we currently support 4 hooks beforeAll
,
afterAll
, beforeEach
, and afterEach
, and also describe.only
, it.only
,
describe.ignore
, and it.ignore
shorthands. Please see
this document
for more details.
Thank you Kyle June for contributing this feature!
Mocking utilities
We added mocking utilities to Deno Standard Modules in this release.
When you’re building software with the external dependencies (such as Twitter’s API, some banking API, etc.) and you can’t control that external system, then testing such software often get really difficult. One way to solve that situation is to use a Mock object which simulates the behavior of the external systems. We now support that way of testing with our mocking utilities.
We added 2 basic mock types: spy
and stub
with assertion functions dedicated
to these 2 types.
spy
is a type of object that records every interaction with it and you can
assert an interaction occurred later. The following example illustrates the
basics.
import {
assertSpyCall,
assertSpyCalls,
spy,
} from "https://deno.land/[email protected]/testing/mock.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("how spy works", () => {
const func = spy();
// The spy isn't called yet
assertSpyCalls(func, 0);
assertEquals(func(), undefined);
// The spy was called with empty args
assertSpyCall(func, 0, { args: [] });
assertSpyCalls(func, 1);
assertEquals(func("x"), undefined);
// The spy was called with "x"
assertSpyCall(func, 1, { args: ["x"] });
assertSpyCalls(func, 2);
});
stub
is the type of object which simulates some predefined behavior. The
following example illustrates the basics.
import {
returnsNext,
stub,
} from "https://deno.land/[email protected]/testing/mock.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("how stub works", () => {
// Replace Math.random with stub
// now it returns 0.1 for the 1st call, 0.2 for the 2nd, 0.3 for the 3rd.
const mathStub = stub(Math, "random", returnsNext([0.1, 0.2, 0.3]));
try {
assertEquals(Math.random(), 0.1);
assertEquals(Math.random(), 0.2);
assertEquals(Math.random(), 0.3);
} finally {
// You need to call .restore() for restoring the original
// behavior of `Math.random`
mathStub.restore();
}
});
We don’t list any realistic examples here as they require lot of space, but please see the document for more details and also examples.
Thank you Kyle June for contributing this feature!
Snapshot testing
We added snapshot testing tools in Deno Standard Modules in this release.
Snapshot testing is a powerful tool for testing software which produces complex output such as generated languages, ASTs, command line outputs, etc.
You can import assertSnapshot
utility from testing/snapshot.ts
which takes
care for creating, updating, and validating the snapshots.
import { assertSnapshot } from "https://deno.land/[email protected]/testing/snapshot.ts";
Deno.test("The generated output matches the snapshot", async (t) => {
const output = generateComplexStuff();
await assertSnapshot(t, output);
});
The above call asserts that the output matches the saved snapshot. When you
don’t have snapshot yet or want to update the snapshot, you can update it by
invoking deno test
command with the --update
option.
$ deno test --allow-read --allow-write -- --update
running 1 test from ./foo/test.ts
The generated output matches the snapshot ... ok (11ms)
> 1 snapshots updated.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (46ms)
Note that you need to give --allow-read
and --allow-write
permissions for
the test process to read and write the snapshot files.
Thank you Yongwook Choi and Ben Heidemann for contributing this feature!
FakeTime
testing utility
We added the FakeTime
testing utility in
Deno Standard Modules in this release.
Testing date time features is sometimes hard because those features often depend
on the current system date time. In this release we added FakeTime
utility in
std/testing/time.ts
that allows you to simulate the system date time and timer
behaviors with it.
import { FakeTime } from "https://deno.land/[email protected]/testing/time.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("test the feature at 2021-12-31", () => {
const time = new FakeTime("2021-12-31");
try {
// now points to the exact same point of time
assertEquals(Date.now(), 1640908800000);
} finally {
time.restore();
}
});
The FakeTime
utility also simulates the behavior of timer functions such as
setTimeout
, setInterval
, etc. You can control the exact elapsed time by
using .tick()
calls.
import { FakeTime } from "https://deno.land/[email protected]/testing/time.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("test the feature at 2021-12-31", () => {
const time = new FakeTime("2021-12-31");
try {
let cnt = 0;
// Starts the interval
setInterval(() => cnt++, 1000);
time.tick(500);
assertEquals(cnt, 0);
// Now 999ms after the start
// the interval callback is still not called
time.tick(499);
assertEquals(cnt, 0);
// Now 1000ms elapsed after the start
time.tick(1);
assertEquals(cnt, 1);
// 3 sec later
time.tick(3000);
assertEquals(cnt, 4);
// You can jump far into the future
time.tick(997000);
assertEquals(cnt, 1001);
} finally {
time.restore();
}
});
Note that you always have to call time.restore()
at the end of the test case
to stop the simulation of the system date time.
Thank you Kyle June for contributing this feature!