On this page

ECMAScript Modules in Deno

Concepts Jump to heading

  • import allows you to include and use modules held elsewhere, on your local file system or remotely.
  • Imports are URLs or file system paths.
  • export allows you to specify which parts of your module are accessible to users who import your module.

Overview Jump to heading

Deno by default standardizes the way modules are imported in both JavaScript and TypeScript using the ECMAScript 6 import/export standard.

It adopts browser-like module resolution, meaning that file names must be specified in full. You may not omit the file extension and there is no special handling of index.js.

import { add, multiply } from "./arithmetic.ts";

Dependencies are also imported directly, there is no package management overhead. Local modules are imported in exactly the same way as remote modules. As the examples show below, the same functionality can be produced in the same way with local or remote modules.

Local Import Jump to heading

In this example the add and multiply functions are imported from a local arithmetic.ts module.

Command: deno run local.ts

/**
 * local.ts
 */
import { add, multiply } from "./arithmetic.ts";

function totalCost(outbound: number, inbound: number, tax: number): number {
  return multiply(add(outbound, inbound), tax);
}

console.log(totalCost(19, 31, 1.2));
console.log(totalCost(45, 27, 1.15));

/**
 * Output
 *
 * 60
 * 82.8
 */

Remote Import Jump to heading

In the local import example above an add and multiply method are imported from a locally stored arithmetic module. The same functionality can be created by importing add and multiply methods from a remote module too.

In this case the Ramda module is referenced, including the version number. Also note a JavaScript module is imported directly into a TypeScript module, Deno has no problem handling this.

Command: deno run ./remote.ts

/**
 * remote.ts
 */
import {
  add,
  multiply,
} from "https://x.nest.land/ramda@0.27.0/source/index.js";

function totalCost(outbound: number, inbound: number, tax: number): number {
  return multiply(add(outbound, inbound), tax);
}

console.log(totalCost(19, 31, 1.2));
console.log(totalCost(45, 27, 1.15));

/**
 * Output
 *
 * 60
 * 82.8
 */

Export Jump to heading

In the local import example above the add and multiply functions are imported from a locally stored arithmetic module. To make this possible the functions stored in the arithmetic module must be exported.

To do this just add the keyword export to the beginning of the function signature as is shown below.

/**
 * arithmetic.ts
 */
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

All functions, classes, constants and variables which need to be accessible inside external modules must be exported. Either by prepending them with the export keyword or including them in an export statement at the bottom of the file.

FAQ Jump to heading

How do I import a specific version of a module? Jump to heading

Specify the version in the URL. For example, this URL fully specifies the code being run: https://unpkg.com/liltest@0.0.5/dist/liltest.js.

It seems unwieldy to import URLs everywhere. Jump to heading

What if one of the URLs links to a subtly different version of a library?

Isn't it error prone to maintain URLs everywhere in a large project?

The solution is to import and re-export your external libraries in a central deps.ts file (which serves the same purpose as Node's package.json file). For example, let's say you were using the above assertion library across a large project. Rather than importing "https://deno.land/std/assert/mod.ts" everywhere, you could create a deps.ts file that exports the third-party code:

deps.ts

export {
  assert,
  assertEquals,
  assertStringIncludes,
} from "https://deno.land/std@0.224.0/assert/mod.ts";

And throughout the same project, you can import from the deps.ts and avoid having many references to the same URL:

import { assertEquals, runTests, test } from "./deps.ts";

This design circumvents a plethora of complexity spawned by package management software, centralized code repositories, and superfluous file formats.

How can I trust a URL that may change? Jump to heading

By using a lock file (with the --lock command line flag), you can ensure that the code pulled from a URL is the same as it was during initial development. You can learn more about this here.

But what if the host of the URL goes down? The source won't be available. Jump to heading

This, like the above, is a problem faced by any remote dependency system. Relying on external servers is convenient for development but brittle in production. Production software should always vendor its dependencies. In Node this is done by checking node_modules into source control. In Deno this is done by adding a vendor field to your deno.json file:

{
  "vendor": true
}

before running your project.