CSSStyleSheet implementation details
Deno implements a small slice of the CSSStyleSheet interface: cssRules returns the sheet's top-level rules as a frozen array (not a live CSSRuleList), creating a fresh array on each access; CSSRule.cssText returns the verbatim text of one top-level rule; replace(text) and replaceSync(text) swap the sheet's contents, with top-level @import rules dropped; new CSSStyleSheet() constructor is available but does not support the options argument (media, disabled, baseURL). Because Deno has no DOM, insertRule and deleteRule are not implemented.
CommonJS support in Deno
Deno runs Node's 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.
File extension determines module system
Deno decides which module system a file uses from its extension and the nearest package.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 a package.json sets "type": "commonjs".
Local import specifiers must include full file extension
With ECMAScript modules, local import specifiers must always include the full file extension. The file extension cannot be omitted. For example, use import { add } from "./calc.ts" instead of import { add } from "./calc".
Dynamic import function
The import() function loads a module on demand and returns a promise for the module's namespace. 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 rarely used features out of the startup path.
Dynamic import permissions
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: local paths need --allow-read and remote URLs need --allow-import.
import.meta properties
Inside any module, import.meta exposes information about the current module. import.meta.url contains the module's URL (e.g., file:///path/to/main.ts), import.meta.main is true if this is the entry module, import.meta.filename contains the local file path (undefined for remote modules), import.meta.dirname contains the directory path (undefined for remote modules), and import.meta.resolve() resolves a specifier to a URL.
Using import.meta.main for dual-purpose modules
import.meta.main is commonly used to make a file work both as an entry point and as an importable library. Check if (import.meta.main) to run code only when the module is executed directly, not when imported by other code.
JSON import with type attribute
Deno supports importing JSON files using the with { type: "json" } import attribute syntax. Example: import data from "./data.json" with { type: "json" }; The imported data is accessible as an object.
Text file imports
Starting with Deno 2.4, text files can be imported using the with { type: "text" } import attribute. The import evaluates to a string. This is stable in Deno 2.8 and no longer requires a flag.
Bytes import with type attribute
Bytes can be imported using the with { type: "bytes" } import attribute, which evaluates to a Uint8Array. This feature is still experimental and requires the --unstable-raw-imports CLI flag or the unstable.raw-import option in deno.json.
CSS stylesheet imports
A stylesheet can be imported with with { type: "css" }, evaluating to a CSSStyleSheet object matching what browsers ship. This is useful for running unmodified browser module graphs in Deno, such as server-side rendering or testing web components. This feature is still experimental and requires the --unstable-raw-imports CLI flag or the unstable.raw-import option in deno.json.
ECMAScript modules as primary module system
Deno uses ECMAScript modules as its primary module system. Code is shared between files with standard import and export statements and runs directly without a bundler or build step. This is the same module system browsers use, keeping code portable across environments.
Dynamic imports with import attributes
Dynamic imports work with import attributes using the same syntax as static imports. Example: const { default: sheet } = await import("./styles.css", { with: { type: "css" } });
Import attribute permission requirements
Static imports and dynamic imports with a statically analyzable specifier are loaded as part of the module graph and require no permission. Only a dynamic import whose specifier cannot be analyzed (such as import(base + "styles.css")) reads the file at runtime and requires read permission (--allow-read).
Deferred module evaluation with import defer
Starting in Deno 2.8, the TC39 Deferred Module Evaluation proposal is supported. The import defer syntax loads a module and its dependencies but does not execute its top-level code until a property is read from the namespace. This defers the cost of modules that are only needed conditionally. The syntax is considered experimental and may change. Example: import defer * as expensive from "./expensive.ts"; console.log(expensive.value); triggers synchronous evaluation.
WebAssembly module imports
Deno supports importing Wasm modules directly. The named exports mirror the Wasm module's exports: functions, memories, and tables come through as their JavaScript objects, and a global export resolves to the value it holds rather than the WebAssembly.Global wrapper, matching the WebAssembly ES module integration.
Data URL imports
Deno supports importing data URLs, allowing import of content that isn't in a separate file. The data URL format is: data:[<media type>][;base64],<data>. For JavaScript modules, use application/javascript as the media type; TypeScript is also supported with application/typescript. This is useful for testing modules in isolation and creating mock modules during tests.
Data URL import examples
Example static import: import * as module from "data:application/javascript;base64,ZXhwb3J0IGNvbnN0IG1lc3NhZ2UgPSBcIkhlbGxvIGZyb20gZGF0YSBVUkxcIjs"; Example dynamic import: const plainModule = await import("data:application/javascript,export function greet() { return 'Hi there!'; }"); const textModule = await import("data:text/plain,export default 'This is plain text'");
Third-party module imports from registries
When working with third-party modules in Deno, use the same import syntax as for local code. Third-party modules are typically imported from a remote registry and start with jsr: or npm:. Example: import { camelCase } from "jsr:@luca/cases@1.0.0"; import { say } from "npm:cowsay@1.6.0";
Import map in deno.json
The imports field in deno.json is called the import map. It centralizes management of remote modules to avoid typing full version specifiers in multiple files. Example: { "imports": { "@luca/cases": "jsr:@luca/cases@^1.0.0", "cowsay": "npm:cowsay@^1.6.0" } }. With remapped specifiers, imports become cleaner: import { camelCase } from "@luca/cases";
Import map with --import-map option requires trailing slash entries
The Import Maps Standard requires two entries for each module: one for the module specifier and another with a trailing /. When using the --import-map import_map.json option, the import_map.json file must include both entries. Example: { "imports": { "@std/async": "jsr:@std/async@^1.0.0", "@std/async/": "jsr:/@std/async@^1.0.0/" } }. An import_map.json referenced by the importMap field in deno.json has the same requirements.
deno.json imports field extends import maps standard
The imports field in deno.json extends the import maps standard. When using the imports field directly in deno.json, you only need to specify the module specifier without the trailing /. Example: { "imports": { "@std/async": "jsr:@std/async@^1.0.0" } }.
Remapped specifier flexibility
In deno.json import maps, the remapped name can be any valid specifier. This is a powerful feature that can remap anything.
deno outdated command
The deno outdated command lists dependencies with newer versions available. Running deno outdated --update bumps versions in deno.json automatically.
deno bump-version command
The deno bump-version command increments a package's version field between releases. Options include patch (1.4.6 -> 1.4.7), minor, major, or a prerelease.
Native TypeScript compiler understands Deno's module resolution
Unlike running the standalone `tsgo` binary, Deno's integration with the native compiler understands Deno's module resolution and types, so `jsr:` and `npm:` specifiers and the `Deno` global all resolve as usual.
TypeScript is a first-class language in Deno
TypeScript files can be run directly with `deno run` without any compiler installation or build step. Configuration is optional; a `tsconfig.json` file works if already present, but is not required to get started.
Deno separates execution and type-checking
Deno treats executing TypeScript and type-checking as two separate concerns. When running `deno run` on a TypeScript file, Deno strips the types and passes the resulting JavaScript to V8; this does not check if types are correct. Type errors do not stop code from running unless explicitly checked with `deno check` or `deno run --check`.
deno check command for type checking
The `deno check` command runs the TypeScript compiler over code without executing it, equivalent to `tsc --noEmit` with strict mode enabled by default. It exits non-zero on errors, making it suitable for CI pipelines.
deno check accepts multiple formats
The `deno check` command accepts the following options: `deno check` to check the whole project, `deno check main.ts` or `deno check src/` to check specific files or directories, `deno check --all main.ts` to also type-check remote modules and npm dependencies, and `deno check --check-js main.js` to type-check JavaScript files without adding @ts-check to each one.
deno run --check for type-checking before execution
Adding `--check` to `deno run` causes type-checking to occur before execution. A type error stops the process before any code runs. Use `deno run --check=all main.ts` to include remote modules and npm packages in the check.
deno test and deno bench type-check by default
`deno test` and `deno bench` type-check by default. Pass `--no-check` to skip type-checking.
Deno strips types and caches emitted JavaScript internally
When executing TypeScript, Deno strips the types and caches the resulting JavaScript internally instead of writing it to disk. There is no `outDir`, `dist/` directory, or source map configuration to manage. Error stack traces still point directly at the original `.ts` sources.
Deno imports require real file extensions
In Deno, imports must use real file extensions on disk. For example, write `import { greet } from "./greet.ts"` to import a file named `greet.ts`. Under `tsc`, you would write `./greet.js` or enable `allowImportingTsExtensions`; Deno requires neither workaround since it never emits files.
Deno runs full TypeScript language including runtime-generating features
Deno runs all TypeScript language features, including those that generate runtime code: enums, namespaces with runtime values, and parameter properties. These features work with no flags. Node's type stripping only handles syntax it can erase and requires the experimental `--experimental-transform-types` flag for these features.
Deno type-checking uses strict mode by default
Deno type-checks in strict mode by default and also enables `noImplicitOverride`, which `tsc` leaves off even under `strict` mode.
Type-check JavaScript files with @ts-check pragma
JavaScript files can be opted into type-checking by adding a `// @ts-check` pragma at the top of the file. This checks the single file; the checker flags the same errors it would in TypeScript.
Type JavaScript with JSDoc comments
JavaScript files can provide type information using JSDoc comments, which the type checker reads. Type annotations cannot be used directly in JavaScript, but JSDoc syntax provides the same information. For example: `/** @param {number} a */` annotates a parameter as a number.
Provide declaration files for untyped JavaScript modules
When importing an untyped JavaScript module from TypeScript, the checker assumes everything exported is `any`. Supply a `.d.ts` declaration file to fix this, unless the JavaScript is already annotated with JSDoc.
Deno does not auto-detect .d.ts files next to .js files
Unlike `tsc`, Deno does not automatically pick up a `.d.ts` file sitting next to a `.js` file with the same basename. You must explicitly specify where the declaration file is, either in the JavaScript source or at the import site.
Use @ts-self-types to declare types in the source
Use the `@ts-self-types` directive in a `.js` file to point to a declaration file, so every importer gets the types for free. For example: `// @ts-self-types="./add.d.ts"` in the JavaScript source.
Use @ts-types to annotate import with declaration file
If you cannot modify the JavaScript source, use the `@ts-types` directive at the import site to annotate where the declaration file is. For example: `// @ts-types="./add.d.ts"` before an import statement, or `// @ts-types="npm:@types/lodash"` to point at a types package.
Add @types packages as dependencies for automatic resolution
For npm packages without type information, add the corresponding `@types` package as a dependency using `deno add`. When `@types` packages live in a local `node_modules` directory, Deno picks them up automatically for every import of the package without requiring `@ts-types` annotations.
Use @ts-types with dynamic imports for typing
The `@ts-types` directive only applies to static `import` statements, not to dynamic `import()`. To type a dynamic import of an untyped module, use a type assertion: `const { markedTerminal } = await import("npm:marked-terminal@7") as typeof import("npm:@types/marked-terminal@6");`
X-TypeScript-Types header for HTTP modules
Servers hosting JavaScript modules can advertise a declaration file in an `X-TypeScript-Types` response header, which Deno resolves relative to the module URL and uses during type checking. CDNs such as esm.sh set this header automatically.
Use declare global with var for augmenting global types
When polyfilling global APIs, use `declare global` with `var` to teach the type checker about the global, since Deno 2 has no `window` object and globals live on `globalThis`. Example: `declare global { var polyfilledAPI: () => string; }`
Avoid global augmentation in favor of ordinary imports
Global augmentation should be avoided when an ordinary import will do, as globals can cause naming conflicts, make code harder to reason about, and are not supported when publishing to JSR.
Deno standard library includes HTTP and file system modules
Deno comes with a standard library that includes modules for common tasks like HTTP servers, file system operations, and more.