The Deno Toolchain
The deno init
Command
Similar to Node.js projects where we get a project going with e.g. npm init
,
Deno provides an init
command to set up the essential files in your directory.
Here’s how you can use it:
deno init my-deno-project
When run without arguments (deno init
), it creates three default files in your
current directory. If you provide an additional argument (like my-deno-project
in the example above), Deno will create a folder with that name and place these
files inside.
The Three Default Files
Let’s see what Deno creates for us:
-
main.ts
- This is the entry point of your app, similar toindex.js
orapp.js
in Node.js projects. It’s where your application’s execution begins. However, you’re not restricted to this name and can choose freely. -
main_test.ts
- As the_test
part suggests, this is where your tests live. Deno has built-in testing capabilities, which we’ll cover in more detail later. This file contains a basic test for the functionality inmain.ts
. It’s good to give the test files the same name as the file that is tested. -
deno.json
- This is the configuration file for a Deno project, similar topackage.json
in Node.js, but with key differences. Here, you can configure various aspects of your project:- Import maps
- Scripts
- Formatter settings
- Linter rules
- TypeScript compiler options
The
deno.json
file is central to customizing your Deno development environment. - Import maps
Running a Deno Project
Once we’ve initialized our project, we can run it using the deno run
command:
deno run main.ts
Give the files created by default this command will execute our main.ts
file
with Add 2 + 3 = 5
printed to the console.
One key difference from Node.js is that Deno doesn’t require a package manager
or a node_modules
folder. All dependencies are fetched and cached on first
run, making project setup much simpler. However if we would like to use a npm
package this possible. How it’s done is described in the next chapter about
using external modules in Deno.
📋 Further Documentation:
Working with External Modules in Deno
Deno has a unique and refreshing approach to including modules and external code. This approach simplifies dependency management and makes your projects more portable.
URL-based Imports
One of the standout features of Deno is its ability to import modules directly from URLs. You can pull code straight from GitHub, or any other source that serves JavaScript or TypeScript files, without needing to install it first. Here’s an example:
import { format } from "https://raw.githubusercontent.com/denoland/std/main/fmt/duration.ts";
console.log(format(99674, { style: "digital" }));
In this example, we’re importing the format
function directly from Deno’s
standard library hosted in raw format on github.com. When running this script
the output should be 00:00:01:39:674:000:000
. There we can see, that we could
work without a package management system after all.
Caching Mechanism
Similar to tools like yarn
or pnpm
Deno uses a local cache of all the
dependencies it has to download. Deno will download and cache modules the first
time you run your script or application. Therefore we need to download them once
and are able to use them in every project without needing a redownload.
When Deno encounters a URL import, it follows these steps:
- Check if the module is in the local cache.
- If not, download the module and store it in the cache.
- Use the cached version for subsequent runs.
This caching mechanism ensures that your code runs consistently and doesn’t unnecessarily re-download modules. The cache is stored in a directory that respects your operating system’s conventions:
- On Linux/macOS:
$HOME/.cache/deno/
- On Windows:
%LOCALAPPDATA%\deno\
(usuallyC:\Users\{UserName}\AppData\Local\deno\
)
If you can’t find a directory there, try using deno cache
which should give
you a similar output like the following:
❯ deno info
DENO_DIR location: /Users/user/Library/Caches/deno
Remote modules cache: /Users/user/Library/Caches/deno/deps
npm modules cache: /Users/user/Library/Caches/deno/npm
Emitted modules cache: /Users/user/Library/Caches/deno/gen
Language server registries cache: /Users/user/Library/Caches/deno/registries
Origin storage: /Users/user/Library/Caches/deno/location_data
There we can see that my remote modules cache can be found under
/Users/user/Library/Caches/deno/deps
. If we take a look on the contents of the
directory, we see how Deno manages those remote modules:
➜ ls /Users/user/Library/Caches/deno/deps/https/
cdn.jsdelivr.net examples.deno.land jsr.io
cdn.sheetjs.com fresh.deno.dev raw.githubusercontent.com
deno.land github.com unpkg.com
esm.sh
Inside of those directories we can find a lot of different files that represent
our JavaScript or Typescript files. If we take a look into one of them we can
see that it is a 1:1 copy. For example looking into
/Users/user/Library/Caches/deno/deps/https/raw.githubusercontent.com/
we might
find our duration.ts
file used earlier. However the file name is in this case
a hash so that Deno can be sure that it knows the file or has to download it.
As you might have read that we have a “npm modules cache” there. We will learn other potential sources of modules in a later chapter. You can be sure, that those will be stored in the cache in the same way.
Security Considerations
While URL imports are powerful, they do raise security concerns. What if the content at a URL changes or becomes malicious? Deno addresses this with several features:
-
Lockfile: You can generate a lockfile (
deno.lock
) that records the hashes of your dependencies. Deno will verify these hashes on subsequent runs. You can usedeno cache --lock=deno.lock <file>
to do this manually. -
Integrity Checking: You can specify a hash in your import statement to ensure you’re getting the exact file you expect:
import { serve } from "https://raw.githubusercontent.com/denoland/std/main/fmt/duration.ts#sha256=abcdef...";
-
Permissions Model: Deno has a robust permissions model that we’ll cover in more detail later. It prevents scripts from accessing resources (like the network or file system) without explicit permission.
Using npm Modules
Deno isn’t limited to URL-based modules. It can use npm
modules directly,
bridging the gap between the Deno and Node.js ecosystems. To use an npm module,
you use the npm:
specifier in front of the module name. For example:
import express from "npm:express";
const app = express();
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(3000);
Running the script in Deno will result in setting up a simple HTTP server with express. This feature allows you to leverage the vast npm ecosystem while enjoying the benefits of Deno’s modern runtime.
If you’re trying this example, you’ll see similar prompts to this:
❯ deno run main.ts -A
┏ ⚠️ Deno requests read access to <CWD>.
┠─ Requested by `Deno.cwd()` API.
┠─ 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) >
This is the implementation of Deno’s security model. We have to explicitly give Deno our permission to use different APIs. The message gives you a lot of hints on where to look for more information. We’ll cover the permission model in detail in our Deno on the CLI chapter.
Managing Dependencies
Unlike Node.js with its package.json
, Deno doesn’t require a central file
listing all dependencies. Instead, dependencies are specified directly in your
code through imports. This approach has several advantages:
- Clear Dependency Tree: You can easily see which file is using which dependency.
- No “Hidden” Dependencies: All dependencies are explicitly imported where they’re used.
- Easy to Remove Unused Dependencies: If you remove an import, you’ve removed the dependency.
However, if you want to manage versions of your dependencies centrally, you can
use import maps. These allow you to specify versions for your imports in a JSON
file, similar to package.json
. Here’s an example:
{
"imports": {
"colors": "https://raw.githubusercontent.com/denoland/std/main/fmt/colors.ts"
}
}
With this import map, you can simply use import { bold } from "colors";
in
your code, and Deno will use the version specified in the import map.
By default Deno uses a deno.json
file with the imports
attribute as a
default way to manage imported modules.
Tradeoffs and what the HTTP imports might bring into your projects Did Ryan Dahl summarize in a blog post.
📋 Further Documentation
JavaScript’s (Not) Missing Standard Library
One of the long-standing criticisms of JavaScript has been its lack of a comprehensive standard library that doesn’t rely on external dependencies. Deno addresses this issue head-on by providing a well-crafted standard library maintained by its core developers and community contributors.
The Deno Standard Library
Deno’s standard library, referred to as std
, is a collection of high-quality,
well-tested modules that cover a wide range of functionalities. These modules
are designed to work seamlessly with Deno and follow its design principles. A
lot of the modules work in NodeJS, Bun or even in the browser.
Key aspects of the Deno standard library:
-
No External Dependencies: All modules in the standard library are implemented without relying on external packages.
-
TypeScript First: The entire library is written in TypeScript, providing excellent type checking and autocompletion in supported editors.
-
Browser APIs: Instead of using custom implementations the library focuses on implementing features with standard Web APIs to ensure compatibility with other runtimes like Bun or Node.
-
Modular Design: Each module is small and focused, allowing you to import only what you need.
-
Continuous Improvement: The library is actively maintained and improved by the Deno core team and community contributors.
Example Modules in the Standard Library
Let’s explore a couple of modules in Deno’s standard library:
-
fs: File system operations
import { readJson, writeJson } from "https://deno.land/std/fs/mod.ts"; const data = await readJson("config.json"); await writeJson("output.json", data);
-
path: Path manipulation utilities
import { join } from "https://deno.land/std/path/mod.ts"; const fullPath = join("directory", "subdirectory", "file.txt");
-
http: HTTP server and client utilities
import { serve } from "https://deno.land/std/http/server.ts"; serve((req) => new Response("Hello, World!"));
-
datetime: Date and time utilities
import { format } from "https://deno.land/std/datetime/mod.ts"; console.log(format(new Date(), "yyyy-MM-dd"));
-
testing: Assertion library for Deno’s built-in test runner
import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; Deno.test("example test", () => { assertEquals(2 + 2, 4); });
Benefits of Using the Standard Library
Using Deno’s standard library offers several advantages:
- Consistency: The modules are designed to work together seamlessly.
- Performance: They’re optimized for use with Deno. (However they work in other runtimes)
- Built-in TypeScript Support: No need for separate
@types
packages.
Versioning and Stability
It’s worth noting that the standard library is versioned independently of Deno itself. When importing from the standard library, you specify a version:
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
This allows you to lock to a specific version of the standard library, ensuring consistency across different environments and over time.
Since importing standard library modules via the “deno.land” url is deprecated, we will now talk about how we import them correctly.
📋 Further Documentation
JSR - JavaScript Registry
JSR, or JavaScript Registry, is an exciting new project initiated by the Deno team. It aims to provide a modern, TypeScript-first package registry that addresses the limitations of npm while being usable across different JavaScript runtimes.
Key Features of JSR
-
TypeScript-First: JSR is designed with TypeScript in mind, making it easier to publish and consume TypeScript packages.
-
Cross-Runtime Compatibility: Packages on JSR can be used not only in Deno, but in Node.js, browsers, and other JavaScript environments.
-
Integrated Documentation: JSR automatically generates documentation from the published modules, making it easier for users to understand and use the packages.
-
Transparency: Package contents are visible directly on the JSR website, increasing trust and making it easier to audit dependencies.
-
Quality Scoring: JSR provides a scoring system that indicates the quality of a package based on factors like documentation, compatibility, and typing.
-
Dependency Information: Clear visibility of both dependencies and reverse dependencies (packages that depend on a given package).
Using JSR in Deno
Using a package from JSR in your Deno project is straightforward:
import { format } from "jsr:@std/fmt/duration";
console.log(format(99674, { style: "digital" }));
Using the first example we did here, we can substitute the long github.com link
with jsr:@std/fmt/duration
- Deno does the rest for us. Downloading, caching
and providing the duration package for us.
JSR vs npm
While JSR isn’t meant to replace npm, it offers several advantages:
- TypeScript-Native: No need for separate
@types
packages or compilation steps. - Simpler Publishing Process: No need for a separate build step before publishing.
- Better Transparency: Easy to see what’s in a package before using it.
- Cross-Runtime by Design: Packages are designed to work across different JavaScript environments.
The Future of JSR
As of now, JSR is in its early stages, but it shows great promise. The Deno team plans to make it a community-driven project, with governance that involves the broader JavaScript community. This approach aims to ensure that JSR evolves to meet the needs of the entire JavaScript ecosystem, not only Deno users.
fmt
Deno’s fmt
command is your go-to tool for code formatting. It’s inspired by
tools like Go’s go fmt
command, aiming to provide a standardized and
opinionated way to format your code.
Under the hood
Deno uses a Rust-based formatter called
dprint
. This ensures fast and consistent
formatting across your project.
What can it format?
Deno’s formatter supports all the file types you’ll commonly use in a Deno project:
- JavaScript (
.js
) and TypeScript (.ts
) - JSX (
.jsx
) and TSX (.tsx
) - Markdown (
.md
,.markdown
) - JSON (
.json
) and JSON with comments (.jsonc
)
💡 Since Deno 1.46 we have support for HTML, CSS, SCSS, Sass, Less, YAML, Astro,
Angular, Svelte and Vue files as well. The formatter for those file types is in
an unstable state for now. Update: Since Deno 2.0.0-rc.10 the formatter for
those file types is stable. You can find the PR #25753
here. This Deno version also adds
support for .vto
and .njk
files. Which are often used in the lume static generator.
Ignoring formatting
Sometimes you might want to keep a specific piece of code formatted in a certain way. Deno allows for this:
-
To ignore formatting for a specific line:
// deno-fmt-ignore const messyButIntentionalCode = doSomethingComplicated( );
-
To ignore an entire file:
// deno-fmt-ignore-file
Customizing the formatter
You can tailor the formatter to your project’s needs by adding a fmt
section
to your deno.json
or deno.jsonc
file:
{
"fmt": {
"useTabs": true,
"lineWidth": 80,
"indentWidth": 4,
"semiColons": true,
"singleQuote": true,
"proseWrap": "preserve",
"include": ["src/"],
"exclude": ["src/testdata/", "data/fixtures/**/*.ts"]
}
}
This configuration allows you to:
- Use tabs instead of spaces
- Set a maximum line width
- Define indentation width
- Choose whether to use semicolons
- Prefer single quotes over double quotes
- Control how markdown prose is wrapped
- Specify which directories to include or exclude from formatting
By setting up your formatting rules, you ensure that your entire team is working with a consistent code style, which can improve readability and reduce merge conflicts.
📋 Further Documentation
lint
Deno comes with its own linter, accessible via the deno lint
command. This
tool goes beyond formatting your code - it helps ensure consistent coding
practices across your project. Think of it as your personal code quality
assistant. It behaves similar to popular tools like eslint
What does the Deno linter do?
The Deno linter checks for a variety of potential issues in your code. Examples are:
- Consistent use of quotation marks (single vs. double)
- Proper usage of
const
instead ofvar
orlet
when variables aren’t reassigned - And many more code quality checks
In total, Deno provides 104 linting rules as of this writing. Out of these, 76 are recommended and enabled by default. This means you get a solid foundation for code quality right out of the box.
Configuring the linter
Like many Deno tools, you can configure the linter in your deno.json
or
deno.jsonc
file. This allows you to set project-wide linting preferences.
Taken from the official documentation is the following example to include or exclude rules or files:\
{
"lint": {
"include": ["src/"],
"exclude": ["src/testdata/", "src/fixtures/**/*.ts"],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": ["no-unused-vars"]
}
}
}
For a comprehensive list of all available lint rules, check out the deno_lint documentation.
Ignoring linter rules
Sometimes, you might need to bypass certain linter rules. Deno makes this easy:
-
To ignore linting for an entire file:
// deno-lint-ignore-file
-
To ignore multiple rules for a file:
// deno-lint-ignore-file no-explicit-any no-empty
-
To ignore a rule for a specific line:
// deno-lint-ignore no-explicit-any const myVar: any = getSomeValue();
Here’s a neat feature: you can add comments explaining why you’re ignoring a rule:
// deno-lint-ignore no-explicit-any -- we really need to use 'any' here due to [reason]
const complexData: any = fetchDataFromExternalAPI();
This is great for maintaining code clarity, when working in teams.
📋 Further Documentation
doc
Documentation is crucial for any project, and Deno makes it easy to generate comprehensive documentation for your code.
How it works
Deno’s documentation generator works by reading the JSDoc
comments in your code. JSDoc is a markup language used to annotate JavaScript
source code files. When you run deno doc
, it parses these comments and
generates documentation based on them.
Basic usage
To generate documentation for a file, run:
deno doc your_file.ts
This will output the documentation to the console. However, there are more useful ways to use this feature.
Generating HTML documentation
You can create a static HTML website of your documentation:
deno doc --html --name="My Project" main.ts
This command:
- Uses the
--html
flag to generate HTML output - Sets the name of your project with
--name
- Specifies the entry point of your project (
main.ts
)
By default, this creates a docs
folder in your current directory with the
generated HTML files.
JSON output
If you prefer machine-readable output, you can generate JSON:
deno doc --json main.ts
This is useful if you want to process the documentation data programmatically.
Linting your documentation
Deno can help ensure your documentation is complete. Use the --lint
flag:
deno doc --lint main.ts
This will warn you about missing documentation or inconsistencies.
For example:
error[missing-jsdoc]: exported symbol is missing JSDoc documentation
--> /Users/user/code/main.ts:8:14
|
8 | export const myFunc = (str: string) => {
| ^
error: Found 1 documentation lint error.
Read more about what the --lint
finds in the
official documentation
Best practices for documentation
- Document all exported symbols (functions, classes, interfaces, etc.).
- Provide clear, concise descriptions for each item.
- Include examples where appropriate.
- Specify types for parameters and return values.
Here’s an example of well-documented code:
/**
* Represents a user in the system.
*/
export interface User {
/**
* The user's full name.
*/
name: string;
/**
* Unique identifier for the user.
*/
id: number;
}
/**
* Retrieves the username from a User object.
* @param {User} user - The user object.
* @returns {string} The user's name.
* @example
* const user = { name: "John Doe", id: 1 };
* const name = getUsername(user);
* console.log(name); // Outputs: "John Doe"
*/
export function getUsername(user: User): string {
return user.name;
}
By following these practices and using Deno’s documentation tools, you can ensure your project is well-documented and easy for others to understand and use.
📋 Further Documentation
test & coverage
Testing is a crucial part of any software development process, and Deno makes it incredibly easy to get started. With Deno’s built-in testing suite, you have no excuse not to test your code!
Writing your first test
Let’s start with a simple example. Say we have a function that adds two numbers
(like the function that we get after running deno init
):
// main.ts
export function add(a: number, b: number): number {
return a + b;
}
We can write a test for this function like so:
// main_test.ts
import { assertEquals } from "jsr:@std/assert";
import { add } from "./main.ts";
Deno.test(function addTest() {
assertEquals(add(2, 3), 5);
});
Here’s what’s happening:
- We import the
assertEquals
function from Deno’s standard library. - We import our
add
function from the main file. - We use
Deno.test
to define a test case. - Inside the test, we use
assertEquals
to check if our function produces the expected result.
Running tests
To run your tests, use the deno test
command in your terminal. Deno will
automatically find and run all files with names ending in _test.ts
or
_test.js
.
Asynchronous tests
Deno makes it easy to test asynchronous code too. Put an async
keyword in
front of your function parentheses in your test:
Deno.test("async function test", async () => {
const result = await someAsyncFunction();
assertEquals(result, expectedValue);
});
This way we can even test functions that work with Promises such as API calls for example.
More advanced testing
Deno’s testing framework allows for more complex scenarios. You can use
t.step()
to create subtests within a larger test:
Deno.test("math operations", async (t) => {
await t.step("addition", () => {
assertEquals(add(2, 3), 5);
});
await t.step("multiplication", () => {
assertEquals(multiply(2, 3), 6);
});
});
This structure helps organize related tests and provides more granular test results. For example creating a user in a database before working with this test user.
Test Documentation Written in JSDoc
Deno also supports running tests and type checks inside JSDoc strings. The only thing we need is to put the code to run inside a block of code in the comment. Below is a sample doc string that shows how it’s done.
/**
* reverse is a function to reverse a string.
*
* # Examples
* ```ts
* import { assertEquals } from "jsr:@std/assert/equals";
*
* const reversed = reverse("hello");
* assertEquals(reversed, "olleh");
* ```
*/
export function reverse(s: string): string {
return s.split("").reverse().join("");
}
If we now run deno test --doc
(the --doc
is important here), we can see in
the output that Deno also executes the examples in our doc strings.
➜ deno test --doc
Check file:///Users/user/deno/main.ts$42-48.ts
Check file:///Users/user/deno/main.ts$57-63.ts
running 1 test from ./main.ts$42-48.ts
file:///Users/user/deno/main.ts$42-48.ts ... ok (0ms)
running 1 test from ./main.ts$57-63.ts
file:///Users/user/deno/main.ts$57-63.ts ... ok (0ms)
ok | 2 passed | 0 failed (12ms)
This is a cool feature when writing libraries, for example, to show people examples and also have tests written at the same time.
If you want to ignore the example from testing you can use the ignore
attribute next to the declaration of the code block language.
/**
* reverse is a function to reverse a string.
*
* # Examples
* ```ts ignore
* import { assertEquals } from "jsr:@std/assert/equals";
*
* const reversed = reverse("hello");
* assertEquals(reversed, "olleh");
* ```
*/
export function reverse(s: string): string {
return s.split("").reverse().join("");
}
This feature was implemented in PR #25220 - so it’s pretty fresh right in time for Deno 2.0.
Test coverage
Deno provides built-in test coverage analysis. To use it, run your tests with
the --coverage
flag:
deno test --coverage
This will create a coverage profile. You can then use the deno coverage
command to analyze this profile:
deno coverage
This will show you which parts of your code are covered by tests and which
aren’t. You can even generate a detailed HTML report of your coverage with the
help of lcov and it’s genhtml
tool:
deno coverage --lcov --output=cov_profile
genhtml -o cov_profile/html cov_profile/lcov.info
This creates an interactive HTML page showing your code coverage, making it easy to identify areas that need more testing.
📋 Further Documentation
bench
Performance is crucial in many applications, and Deno provides a built-in benchmarking tool to help you measure and optimize your code’s performance.
What is benchmarking?
Benchmarking is the process of measuring the performance of a piece of code. It helps you understand how fast your code runs and can be crucial for identifying bottlenecks or comparing different implementations.
Basic usage
Let’s start with a simple benchmark. Say we have a function we want to measure:
// main.ts
export const add = (a: number, b: number): number {
return a + b;
}
We can create a benchmark for this function like this:
// bench.ts
import { add } from "./main.ts";
Deno.bench("addition benchmark", () => {
add(2, 3);
});
To run this benchmark, use the deno bench
command:
deno bench bench.ts
Deno will run your benchmark multiple times and provide statistics about its performance.
Understanding the output
When you run a benchmark, you’ll see output in a table similar to this:
cpu: Apple M1
runtime: deno 1.40.3 (aarch64-apple-darwin)
file:///Users/user/project/bench.ts
benchmark time (avg) iter/s (min … max) p75 p99 p995
-------------------------------------------------- -----------------------------
addition benchmark 4.08 ns/iter 245,136,869.3 (3.99 ns … 9.9 ns) 4.09 ns 4.35 ns 4.4 ns
This output tells you:
- The average time per iteration
- How many iterations can be performed per second
- The range of times (minimum to maximum)
- The 75th, 99th, and 99.5th percentiles
When you have multiple files with multiple benchmarks Deno will go through all of them and run them one by one. This might take a moment but will result in output like this:
➜ deno bench -A
Check file:///Users/user/code/projects/deno-course/code/bench/read_bench.ts
Check file:///Users/user/code/projects/deno-course/code/bench/sort_bench.ts
cpu: Apple M1
runtime: deno 1.46.1 (aarch64-apple-darwin)
file:///Users/user/code/projects/deno-course/code/bench/read_bench.ts
benchmark time (avg) iter/s (min … max) p75 p99 p995
------------------------------------------------------------------------------------ -----------------------------
readPokedex 3.73 ms/iter 268.0 (3.61 ms … 4.19 ms) 3.76 ms 4.19 ms 4.19 ms
readPokedex with start and end 6.82 µs/iter 146,692.1 (6.25 µs … 26.46 µs) 6.75 µs 11 µs 26.46 µs
file:///Users/user/code/projects/deno-course/code/bench/sort_bench.ts
benchmark time (avg) iter/s (min … max) p75 p99 p995
----------------------------------------------------------------------- -----------------------------
Array.sort Arr1 92.29 ns/iter 10,835,411.7 (88.67 ns … 105.1 ns) 94.04 ns 102.99 ns 103.9 ns
Array.sort Arr2 100.98 ns/iter 9,902,601.0 (97.12 ns … 124.71 ns) 102.72 ns 107.85 ns 110.35 ns
MergeSorting Arr1 854.86 ns/iter 1,169,784.5 (839.41 ns … 921.54 ns) 858.71 ns 921.54 ns 921.54 ns
MergeSorting Arr2 1.07 µs/iter 938,260.4 (1.05 µs … 1.14 µs) 1.07 µs 1.14 µs 1.14 µs
quickSorting Arr1 569.39 ns/iter 1,756,266.6 (557.1 ns … 631.91 ns) 574.1 ns 631.91 ns 631.91 ns
quickSorting Arr2 724.42 ns/iter 1,380,410.7 (710.2 ns … 764.66 ns) 729.67 ns 764.66 ns 764.66 ns
More complex benchmarks
You can create more sophisticated benchmarks using additional options:
Deno.bench({
name: "complex addition benchmark",
fn: () => {
add(Math.random() * 1000, Math.random() * 1000);
},
baseline: true,
group: "math operations",
});
This benchmark:
- Has a custom name
- Uses a more complex function (with random numbers)
- Is marked as a baseline (useful for comparing different implementations)
- Belongs to a group (helpful for organizing related benchmarks even across files)
Benchmarking asynchronous code
Similar or even same as the asynchronous test
functions we can even benchmark
asynchronous functions. This is done in the same way putting the async
in
front of the parentheses. Regular Javascript syntax.
Deno.bench("async operation", async () => {
await someAsyncOperation();
});
Controlling benchmark execution
Sometimes, you might want to control what part of your code is being timed. You
can do this using b.start()
and b.end()
:
Deno.bench("partial operation", (b) => {
// Setup code (not timed)
const data = prepareData();
b.start();
// This is the part that will be timed
processData(data);
b.end();
// Cleanup code (not timed)
cleanupData(data);
});
This allows you to exclude setup and cleanup time from your benchmark.
💡 Note: Calling the benchmarking parameter b
is your choice.
If you want to know more about the deno bench
command. There is a lot of
documentation about this in the
deno bench article
and in the Deno.bench API docs.
Best practices for benchmarking
- Run benchmarks multiple times to account for variability.
- Be aware of environmental factors (CPU load, etc.) that might affect results.
- Compare benchmarks on the same machine for consistency.
- Use realistic data and operations in your benchmarks.
By using Deno’s benchmarking tools, you can gain valuable insights into your code’s performance and make informed optimizations.
📋 Further Documentation
compile
One of my favorite built in tools of Deno is a powerful tool that allows you to create standalone executables from your TypeScript or JavaScript code. This can be incredibly useful for distributing your applications to users who don’t have Deno installed.
Basic usage
To compile a Deno application, use the deno compile
command:
deno compile main.ts
This will create an executable file named after your source directory (e.g.,
foo
or foo.exe
on Windows). The binary is default for your CPU architecture
of your host machine by default.
Cross-compilation
One of the most powerful features of Deno’s compiler is its ability to cross-compile for different platforms. You can create executables for Windows, macOS, or Linux, regardless of your development platform.
To specify a target, use the --target
flag:
deno compile --target x86_64-pc-windows-msvc main.ts
This would create a Windows executable, even if you’re developing on macOS or Linux.
Available targets include:
x86_64-unknown-linux-gnu
for x86 (Intel or AMD) Linuxaarch64-unknown-linux-gnu
for ARM-based Linuxx86_64-pc-windows-msvc
for x86 Windowsx86_64-apple-darwin
for Intel-based macOSaarch64-apple-darwin
for ARM-based Macs
💡 Note: You can find the architecture that Deno is running on when checking the
Deno --version
command.
Specifying permissions
When compiling, you need to specify all the permissions your application will need at runtime. For example:
deno compile --allow-read --allow-write main.ts
This creates an executable with permissions to read from and write to the file system.
Considerations
While Deno’s compile feature is powerful, there are a few things to keep in mind:
-
File size: Compiled executables can be large (50MB or more) because they include the Deno runtime.
-
Performance: There might be a slight startup delay compared to running with
deno run
, as the executable needs to unpack the bundled code. -
Updates: The compiled executable is static. If you update your dependencies, you’ll need to recompile.
-
Platform specificity: While you can cross-compile, the resulting executable is specific to the target platform and architecture.
Best practices
-
Minimize dependencies: The more external dependencies your project has, the larger the compiled executable will be.
-
Test thoroughly: Make sure to test your compiled executable in an environment similar to where it will be deployed.
-
Version your executables: Keep track of which source code version each executable was compiled from.
-
Document required permissions: Make sure users know what system access your application requires.
Example: Creating a CLI tool
Let’s say you’ve created a useful command-line tool with Deno. Here’s how you might compile and distribute it:
// cli.ts
import { parse } from "https://deno.land/std/flags/mod.ts";
const { args } = Deno;
const parsedArgs = parse(args);
if (parsedArgs.help) {
console.log("Usage: cli [options]");
Deno.exit(0);
}
console.log("Hello from my CLI tool!");
// ... rest of your CLI logic
To compile this:
deno compile cli.ts
📋 Further Documentation
Understanding import.meta
import.meta
is a powerful feature in modern JavaScript that provides metadata
about the current module. It’s part of the ECMAScript specification and is
supported in modern browsers and JavaScript runtimes, including Deno.
What is import.meta?
import.meta
is an object that contains contextual information about the module
in which it’s accessed. The exact properties available on import.meta
can vary
depending on the environment, but Deno provides several useful properties.
Key Properties in Deno
-
import.meta.url
: This provides the URL of the current module. In Deno:- For local scripts (e.g.,
deno run main.ts
), it’s afile://
URL pointing to the script’s location on the filesystem. - For remote scripts, it’s the full URL of the script.
- For local scripts (e.g.,
-
import.meta.main
: This boolean property istrue
if the current module is the main module that was directly run by Deno. It’s used to write code that should execute when a file is run directly, not when it’s imported by another module. -
import.meta.resolve(specifier)
: This method resolves a module specifier relative to the current module.
Practical Uses of import.meta
Let’s look at practical examples of how you might use import.meta
in your Deno
projects:
-
Determining if a module is the main module:
if (import.meta.main) { console.log("This code only runs if this module is the main module"); }
This is even done in the
main.ts
file after runningdeno init
. -
Getting the directory of the current module:
import { dirname } from "jsr:@std/path"; const currentDir = dirname(new URL(import.meta.url).pathname); console.log(`This module is located in: ${currentDir}`);
import.meta vs __dirname and __filename
If you’re coming from Node.js, you might be used to __dirname
and
__filename
. Deno doesn’t have these globals, but you can achieve the same
results with import.meta.url
:
import { fromFileUrl } from "jsr:@std/path";
const __filename = fromFileUrl(import.meta.url);
const __dirname = new URL(".", import.meta.url).pathname;
This approach is more explicit about the source of these values and works consistently across different environments.
📋 Further Documentation
Wrapping Up the Deno Toolchain
Concluding this whole chapter of the Deno toolbox you can see that we’re getting a lot out of the box. The whole project is set up in a way that developers can start writing code right away without the need of setting up Typescript, linting, formatting or anything. It all just works.
In the next chapter we will talk about using Deno as a runtime to build a simple CLI tool. We’ll learn about the permission model that makes applications more secure through disabling any permissions (e.g. network, filesystem) by default having to explicitly giving the script our allowance.