Skip to main content
On this page

Modules and dependencies

Deno uses ECMAScript modules as its default module system to align with modern JavaScript standards and to promote a more efficient and consistent development experience. It's the official standard for JavaScript modules, allows for better tree-shaking, improved tooling integration, and native support across different environments.

By adopting ECMAScript modules, Deno ensures compatibility with the ever-evolving JavaScript ecosystem. For developers, this means a streamlined and predictable module system that avoids the complexities associated with legacy module formats like CommonJS.

Importing modules Jump to heading

In this example the add function is imported from a local calc.ts module.

calc.ts
export function add(a: number, b: number): number {
  return a + b;
}
main.ts
// imports the `calc.ts` module next to this file
import { add } from "./calc.ts";

console.log(add(1, 2)); // 3

You can run this example by calling deno run main.ts in the directory that contains both main.ts and calc.ts.

With ECMAScript modules, local import specifiers must always include the full file extension. It cannot be omitted.

example.ts
// WRONG: missing file extension
import { add } from "./calc";

// CORRECT: includes file extension
import { add } from "./calc.ts";

Importing third party modules and libraries Jump to heading

When working with third-party modules in Deno, use the same import syntax as you do for local code. Third party modules are typically imported from a remote registry and start with jsr: , npm: or https://.

main.ts
import { camelCase } from "jsr:@luca/cases@1.0.0";
import { say } from "npm:cowsay@1.6.0";
import { pascalCase } from "https://deno.land/x/case/mod.ts";

Deno recommends JSR, the modern JavaScript registry, for third party modules. There, you'll find plenty of well documented ES modules for your projects, including the Deno Standard Library.

You can read more about Deno's support for npm packages here.

Managing third party modules and libraries Jump to heading

Typing out the module name with the full version specifier can become tedious when importing them in multiple files. You can centralize management of remote modules with an imports field in your deno.json file. We call this imports field the import map, which is based on the Import Maps Standard.

deno.json
{
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0",
    "cowsay": "npm:cowsay@^1.6.0",
    "cases": "https://deno.land/x/case/mod.ts"
  }
}

With remapped specifiers, the code looks cleaner:

main.ts
import { camelCase } from "@luca/cases";
import { say } from "cowsay";
import { pascalCase } from "cases";

The remapped name can be any valid specifier. It's a very powerful feature in Deno that can remap anything. Learn more about what the import map can do here.

Adding dependencies with deno add Jump to heading

The installation process is made easy with the deno add subcommand. It will automatically add the latest version of the package you requested to the imports section in deno.json.

# Add the latest version of the module to deno.json
$ deno add jsr:@luca/cases
Add @luca/cases - jsr:@luca/cases@1.0.0
deno.json
{
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0"
  }
}

You can also specify an exact version:

# Passing an exact version
$ deno add jsr:@luca/cases@1.0.0
Add @luca/cases - jsr:@luca/cases@1.0.0

Read more in deno add reference.

You can also remove dependencies using deno remove:

$ deno remove @luca/cases
Remove @luca/cases
deno.json
{
  "imports": {}
}

Read more in deno remove reference.

Package Versions Jump to heading

It is possible to specify a version range for the package you are importing. This is done using the @ symbol followed by a version range specifier, and follows the semver versioning scheme.

For example:

@scopename/mypackage           # highest version
@scopename/mypackage@16.1.0    # exact version
@scopename/mypackage@16        # highest 16.x version >= 16.0.0
@scopename/mypackage@^16.1.0   # highest 16.x version >= 16.1.0
@scopename/mypackage@~16.1.0   # highest 16.1.x version >= 16.1.0

Here is an overview of all the ways you can specify a version or a range:

Symbol Description Example
1.2.3 An exact version. Only this specific version will be used. 1.2.3
^1.2.3 Compatible with version 1.2.3. Allows updates that do not change the leftmost non-zero digit.
For example, 1.2.4 and 1.3.0 are allowed, but 2.0.0 is not.
^1.2.3
~1.2.3 Approximately equivalent to version 1.2.3. Allows updates to the patch version.
For example, 1.2.4 is allowed, but 1.3.0 is not.
~1.2.3
>=1.2.3 Greater than or equal to version 1.2.3. Any version 1.2.3 or higher is allowed. >=1.2.3
<=1.2.3 Less than or equal to version 1.2.3. Any version 1.2.3 or lower is allowed. <=1.2.3
>1.2.3 Greater than version 1.2.3. Only versions higher than 1.2.3 are allowed. >1.2.3
<1.2.3 Less than version 1.2.3. Only versions lower than 1.2.3 are allowed. <1.2.3
1.2.x Any patch version within the minor version 1.2. For example, 1.2.0, 1.2.1, etc. 1.2.x
1.x Any minor and patch version within the major version 1. For example, 1.0.0, 1.1.0, 1.2.0, etc. 1.x
* Any version is allowed. *

HTTPS imports Jump to heading

Deno also supports import statements that reference HTTP/HTTPS URLs, either directly:

import { Application } from "https://deno.land/x/oak/mod.ts";

or part of your deno.json import map:

{
  "imports": {
    "oak": "https://deno.land/x/oak/mod.ts"
  }
}

Supporting HTTPS imports enables us to support the following JavaScript CDNs, as they provide URL access to JavaScript modules:

HTTPS imports are useful if you have a small, often single file, Deno project that doesn't require any other configuration. With HTTPS imports, you can avoid having a deno.json file at all. It is not advised to use this style of import in larger applications however, as you may end up with version conflicts (where different files use different version specifiers).

Info

Use HTTPS imports with caution, and only from trusted sources. If the server is compromised, it could serve malicious code to your application. They can also cause versioning issues if you import different versions in different files. HTTPS imports remain supported, but we recommend using a package registry for the best experience.

Overriding HTTPS imports Jump to heading

The other situation where import maps can be very useful is to override HTTPS imports in specific modules.

Let's say you want to override a https://deno.land/x/my-library@1.0.0/mod.ts specifier that is used inside files coming from https://deno.land/x/example/ to a local patched version. You can do this by using a scope in the import map that looks something like this:

{
  "imports": {
    "example/": "https://deno.land/x/example/"
  },
  "scopes": {
    "https://deno.land/x/example/": {
      "https://deno.land/x/my-library@1.0.0/mod.ts": "./patched/mod.ts"
    }
  }
}

Note

HTTPS imports have no notion of packages. Only the import map at the root of your project is used. Import maps used inside URL dependencies are ignored.

Publishing modules Jump to heading

Any Deno program that defines an export can be published as a module. This allows other developers to import and use your code in their own projects. Modules can be published to:

  • JSR - recommended, supports TypeScript natively and auto-generates documentation for you
  • npm - use dnt to create the npm package
  • deno.land/x - for HTTPS imports, use JSR instead if possible

Reloading modules Jump to heading

By default, Deno uses a global cache directory (DENO_DIR) for downloaded dependencies. This cache is shared across all projects.

You can force deno to refetch and recompile modules into the cache using the --reload flag.

# Reload everything
deno run --reload my_module.ts

# Reload a specific module
deno run --reload=jsr:@std/fs my_module.ts

Using only cached modules Jump to heading

To force Deno to only use modules that have previously been cached, use the --cached-only flag:

deno run --cached-only mod.ts

This will fail if there are any dependencies in the dependency tree for mod.ts which are not yet cached.

Vendoring remote modules Jump to heading

If your project has external dependencies, you may want to store them locally to avoid downloading them from the internet every time you build your project. This is especially useful when building your project on a CI server or in a Docker container, or patching or otherwise modifying the remote dependencies.

Deno offers this functionality through a setting in your deno.json file:

{
  "vendor": true
}

Add the above snippet to your deno.json file and Deno will cache all dependencies locally in a vendor directory when the project is run, or you can optionally run the deno install --entrypoint command to cache the dependencies immediately:

deno install --entrypoint main.ts

You can then run the application as usual with deno run:

deno run main.ts

After vendoring, you can run main.ts without internet access by using the --cached-only flag, which forces Deno to use only locally available modules.

Integrity Checking and Lock Files Jump to heading

Imagine your module relies on a remote module located at https://some.url/a.ts. When you compile your module for the first time, a.ts is fetched, compiled, and cached. This cached version will be used until you either run your module on a different machine (such as in a production environment) or manually reload the cache (using a command like deno install --reload).

But what if the content at https://some.url/a.ts changes? This could result in your production module running with different dependency code than your local module. To detect this, Deno uses integrity checking and lock files.

Deno uses a deno.lock file to check external module integrity. To opt into a lock file, either:

  1. Create a deno.json file in the current or an ancestor directory, which will automatically create an additive lockfile at deno.lock.

    Note that this can be disabled by specifying the following in your deno.json:

    deno.json
    {
      "lock": false
    }
    
  2. Use the --lock flag to enable and specify lock file checking.

Frozen lockfile Jump to heading

By default, Deno uses an additive lockfile, where new dependencies are added to the lockfile instead of erroring.

This might not be desired in certain scenarios (ex. CI pipelines or production environments) where you'd rather have Deno error when it encounters a dependency it's never seen before. To enable this, you can specify the --frozen flag or set the following in a deno.json file:

deno.json
{
  "lock": {
    "frozen": true
  }
}

When running a deno command with a frozen lockfile, any attempts to update the lockfile with new contents will cause the command to exit with an error showing the modifications that would have been made.

If you wish to update the lockfile, specify --frozen=false on the command line to temporarily disable the frozen lockfile.

Changing lockfile path Jump to heading

The lockfile path can be configured by specifying --lock=deps.lock or the following in a Deno configuration file:

deno.json
{
  "lock": {
    "path": "deps.lock"
  }
}

Private repositories Jump to heading

Note

If you're looking for private npm registries and .npmrc support, visit the npm support page.

There may be instances where you want to load a remote module that is located in a private repository, like a private repository on GitHub.

Deno supports sending bearer tokens when requesting a remote module. Bearer tokens are the predominant type of access token used with OAuth 2.0, and are broadly supported by hosting services (e.g., GitHub, GitLab, Bitbucket, Cloudsmith, etc.).

DENO_AUTH_TOKENS Jump to heading

The Deno CLI will look for an environment variable named DENO_AUTH_TOKENS to determine what authentication tokens it should consider using when requesting remote modules. The value of the environment variable is in the format of n number of tokens delimited by a semi-colon (;) where each token is either:

  • a bearer token in the format of {token}@{hostname[:port]} or
  • basic auth data in the format of {username}:{password}@{hostname[:port]}

For example, a single token for deno.land would look something like this:

DENO_AUTH_TOKENS=a1b2c3d4e5f6@deno.land

or:

DENO_AUTH_TOKENS=username:password@deno.land

And multiple tokens would look like this:

DENO_AUTH_TOKENS=a1b2c3d4e5f6@deno.land;f1e2d3c4b5a6@example.com:8080;username:password@deno.land

When Deno goes to fetch a remote module, where the hostname matches the hostname of the remote module, Deno will set the Authorization header of the request to the value of Bearer {token} or Basic {base64EncodedData}. This allows the remote server to recognize that the request is an authorized request tied to a specific authenticated user, and provide access to the appropriate resources and modules on the server.

GitHub Jump to heading

To access private repositories on GitHub, you would need to issue yourself a personal access token. You do this by logging into GitHub and going under Settings -> Developer settings -> Personal access tokens:

Personal access tokens settings on GitHub

You would then choose to Generate new token and give your token a description and appropriate access to the repo scope. The repo scope will enable reading file contents (more on scopes in the GitHub docs):

Creating a new personal access token on GitHub

And once created GitHub will display the new token a single time, the value of which you would want to use in the environment variable:

Display of newly created token on GitHub

In order to access modules that are contained in a private repository on GitHub, you would want to use the generated token in the DENO_AUTH_TOKENS environment variable scoped to the raw.githubusercontent.com hostname. For example:

DENO_AUTH_TOKENS=a1b2c3d4e5f6@raw.githubusercontent.com

This should allow Deno to access any modules that the user who the token was issued for has access to.

When the token is incorrect, or the user does not have access to the module, GitHub will issue a 404 Not Found status, instead of an unauthorized status. So if you are getting errors that the modules you are trying to access are not found on the command line, check the environment variable settings and the personal access token settings.

In addition, deno run -L debug should print out a debug message about the number of tokens that are parsed out of the environment variable. It will print an error message if it feels any of the tokens are malformed. It won't print any details about the tokens for security purposes.