Modules (original) (raw)
Deno usesECMAScript modulesas its primary module system, the official standard for JavaScript. You share code between files with standard import and export statements and run it directly, with no bundler or build step. This is the same module system browsers use, so the code you write stays portable across environments.
Deno also runs Node's older CommonJS modules (require and module.exports), so existing npm packages and Node projects work without conversion. The two systems are fully supported and interoperate, but ECMAScript modules are recommended for new code; reach for CommonJS mainly when a dependency or an existing project still uses it.
Deno decides which system a file uses from its extension and the nearestpackage.json: a .mjs file is always an ES module, a .cjs file is always CommonJS, and .js and .ts files are treated as ES modules unless apackage.json sets "type": "commonjs". The rest of this page covers ES modules; see CommonJS supportfor how require, module resolution, and ESM/CommonJS interop work.
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";
Dynamic imports Jump to heading
The import statements above are static: the specifier is a string literal and the module loads before your code runs. You can also load a module on demand with the import() function, which returns a promise for the module's namespace:
main.ts
const { add } = await import("./calc.ts");
console.log(add(1, 2)); // 3
Because import() runs at runtime, the specifier can be computed and the module is only fetched and evaluated when the call executes. This is useful for loading code conditionally or keeping a rarely used feature out of the startup path:
if (Deno.args.includes("--greet")) {
const { greet } = await import("./greet.ts");
greet();
}
A dynamic import() whose specifier is a string literal is part of the static module graph and loads without extra permissions. One whose specifier is computed at runtime is checked against the permission system when it runs: local paths need --allow-read and remote URLs need --allow-import. SeeSecurity for details.
Inside any module, import.meta exposes information about the current module and helpers for resolving paths relative to it:
main.ts
console.log(import.meta.url); // file:///path/to/main.ts
console.log(import.meta.main); // true if this is the entry module
console.log(import.meta.filename); // /path/to/main.ts (local modules only)
console.log(import.meta.dirname); // /path/to (local modules only)
console.log(import.meta.resolve("./data.json")); // resolves a specifier to a URL
import.meta.main is a common way to make a file work both as an entry point and as an importable library:
server.ts
export function createServer() {/* ... */}
// Only start the server when run directly, not when imported.
if (import.meta.main) {
createServer();
}
filename and dirname are undefined for remote modules. For a full walkthrough, see themodule metadata tutorial.
Import attributes Jump to heading
Deno supports the with { type: "json" } import attribute syntax for importing JSON files:
import data from "./data.json" with { type: "json" };
console.log(data.property); // Access JSON data as an object
Starting with Deno 2.4 it's possible to import text and bytes modules too.
import text from "./log.txt" with { type: "text" };
console.log(typeof text === "string");
// true
console.log(text);
// Hello from a text file
text imports
Stable in Deno 2.8 and no longer require a flag.
import bytes from "./image.png" with { type: "bytes" };
console.log(bytes instanceof Uint8Array);
// true
console.log(bytes);
// Uint8Array(12) [
// 72, 101, 108, 108, 111,
// 44, 32, 68, 101, 110,
// 111, 33
// ]
bytes imports
Still experimental. Enable with the --unstable-raw-imports CLI flag or theunstable.raw-import option indeno.json.
Deferred module evaluation Jump to heading
Starting in Deno 2.8, theTC39 Deferred Module Evaluation proposalis supported. The import defer syntax loads a module (including its dependencies) but does not execute its top-level code until you first read a property from the namespace:
main.ts
import defer * as expensive from "./expensive.ts";
console.log("startup is fast: expensive.ts has not run yet");
// Touching any property triggers synchronous evaluation
console.log(expensive.value);
Use it to defer the cost of modules that are only needed conditionally; for example, error-path code in a CLI tool or feature flags whose implementation is heavy to initialize.
The proposal is still at TC39 Stage 3, so the syntax is considered experimental and may change. Standard import remains the right default.
WebAssembly modules Jump to heading
Deno supports importing Wasm modules directly:
import { add } from "./add.wasm";
console.log(add(1, 2));
To learn more, visitWebAssembly section
Data URL imports Jump to heading
Deno supports importing of data URLs, which allows you to import content that isn't in a separate file. This is useful for testing, prototyping, or when you need to programmatically generate modules.
You can create modules on the fly using the data: URL scheme:
// Import a simple JavaScript module from a data URL
import * as module from "data:application/javascript;base64,ZXhwb3J0IGNvbnN0IG1lc3NhZ2UgPSAiSGVsbG8gZnJvbSBkYXRhIFVSTCI7";
console.log(module.message); // Outputs: Hello from data URL
// You can also use the non-base64 format
const plainModule = await import(
"data:application/javascript,export function greet() { return 'Hi there!'; }"
);
console.log(plainModule.greet()); // Outputs: Hi there!
// A simpler example with text content
const textModule = await import(
"data:text/plain,export default 'This is plain text'"
);
console.log(textModule.default); // Outputs: This is plain text
The data URL format follows this pattern:
>_
data:[<media type>][;base64],<data>
For JavaScript modules, use application/javascript as the media type. TypeScript is also supported with application/typescript. This feature is particularly useful for testing modules in isolation and creating mock modules during tests.
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 canread 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 importsfield 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 in theconfiguration dependencies section.
Differentiating between imports or importMap in deno.json and --import-map option Jump to heading
The Import Maps Standard requires two entries for each module: one for the module specifier and another for the specifier with a trailing /. This is because the standard allows only one entry per module specifier, and the trailing / indicates that the specifier refers to a directory. For example, when using the --import-map import_map.json option, the import_map.json file must include both entries for each module (note the use of jsr:/@std/asyncinstead of jsr:@std/async):
import_map.json
{
"imports": {
"@std/async": "jsr:@std/async@^1.0.0",
"@std/async/": "jsr:/@std/async@^1.0.0/"
}
}
An import_map.json file referenced by the importMap field in deno.jsonbehaves exactly the same as using the --import-map option, with the same requirements for including both entries for each module as shown above.
In contrast, deno.json extends the import maps standard. When you use the imports field in deno.json, you only need to specify the module specifier without the trailing /:
deno.json
{
"imports": {
"@std/async": "jsr:@std/async@^1.0.0"
}
}
Managing dependencies Jump to heading
Now that you understand how Deno resolves and imports modules, see theDependency management guide for the day-to-day tasks: adding and removing packages with deno add / deno remove, pinning versions, overriding and vendoring dependencies, lockfiles and integrity checking, supply chain management, publishing your own modules, and using private registries.