Skip to main content

Overview of TypeScript in Deno

One of the benefits of Deno is that it treats TypeScript as a first class language, just like JavaScript or WebAssembly, when running code in Deno. What that means is you can run or import TypeScript without installing anything more than the Deno CLI.

But wait a minute, does Deno really run TypeScript? you might be asking yourself. Well, depends on what you mean by run. One could argue that in a browser you don't actually run JavaScript either. The JavaScript engine in the browser translates the JavaScript to a series of operation codes, which it then executes in a sandbox. So it translates JavaScript to something close to assembly. Even WebAssembly goes through a similar translation, in that WebAssembly is architecture agnostic while it needs to be translated into the machine specific operation codes needed for the particular platform architecture it is running on. So when we say TypeScript is a first class language in Deno, we mean that we try to make the user experience in authoring and running TypeScript as easy and straightforward as JavaScript and WebAssembly.

Behind the scenes, we use a combination of technologies, in Rust and JavaScript, to provide that experience.

How does it work?

At a high level, Deno converts TypeScript (as well as TSX and JSX) into JavaScript. It does this via a combination of the TypeScript compiler, which we build into Deno, and a Rust library called swc. When the code has been type checked and transformed, it is stored in a cache, ready for the next run without the need to convert it from its source to JavaScript again.

You can see this cache location by running deno info:

> deno info
DENO_DIR location: "/path/to/cache/deno"
Remote modules cache: "/path/to/cache/deno/deps"
TypeScript compiler cache: "/path/to/cache/deno/gen"

If you were to look in that cache, you would see a directory structure that mimics that source directory structure and individual .js and .meta files (also potentially .map files). The .js file is the transformed source file while the .meta file contains meta data we want to cache about the file, which at the moment contains a hash of the source module that helps us manage cache invalidation. You might also see a .buildinfo file as well, which is a TypeScript compiler incremental build information file, which we cache to help speed up type checking.

Type Checking

One of the main advantages of TypeScript is that you can make code more type safe, so that what would be syntactically valid JavaScript becomes TypeScript with warnings about being "unsafe".

You can type-check your code (without executing it) using the following command:

deno check module.ts
# or also type check remote modules and npm packages
deno check --all module.ts

Type checking can take a significant amount of time, especially if you are working on a code base where you are making a lot of changes. We have tried to optimize type checking, but it still comes at a cost. Therefore, by default, TypeScript modules are not type-checked before they are executed.

deno run module.ts

When using the command above, Deno will simply transpile the module before executing it, ignoring any potential type-related issues. In order to perform a type-check of the module before execution occurs, the --check argument must be used with deno run:

deno run --check module.ts
# or also type check remote modules and npm packages
deno run --check=all module.ts

While tsc will (by default) still emit JavaScript when encountering diagnostic (type-checking) issues, Deno currently treats them as terminal. When using deno run with the --check argument, type-related diagnostics will prevent the program from running: it will halt on these warnings, and exit the process before executing the code.

In order to avoid this, you will either need to resolve the issue, utilise the // @ts-ignore or // @ts-expect-error pragmas, or skip type checking all together.

You can learn more about type-checking arguments here.

Determining the type of file

Since Deno supports JavaScript, TypeScript, JSX, TSX modules, Deno has to make a decision about how to treat each of these kinds of files. For local modules, Deno makes this determination based on the extension. When the extension is absent in a local file, it is assumed to be JavaScript. The module type can be overridden using the --ext argument. This is useful if the module does not have a file extension, e.g. because it is embedded in another file.

For remote modules, the media type (mime-type) is used to determine the type of the module, where the path of the module is used to help influence the file type, when it is ambiguous what type of file it is.

For example, a .d.ts file and a .ts file have different semantics in TypeScript as well as have different ways they need to be handled in Deno. While we expect to convert a .ts file into JavaScript, a .d.ts file contains no "runnable" code, and is simply describing types (often of "plain" JavaScript). So when we fetch a remote module, the media type for a .ts. and .d.ts file looks the same. So we look at the path, and if we see something that has a path that ends with .d.ts we treat it as a type definition only file instead of "runnable" TypeScript.

Supported media types

The following table provides a list of media types which Deno supports when identifying the type of file of a remote module:

Media TypeHow File is Handled
application/typescriptTypeScript (with path extension influence)
text/typescriptTypeScript (with path extension influence)
video/vnd.dlna.mpeg-ttsTypeScript (with path extension influence)
video/mp2tTypeScript (with path extension influence)
application/x-typescriptTypeScript (with path extension influence)
application/javascriptJavaScript (with path extensions influence)
text/javascriptJavaScript (with path extensions influence)
application/ecmascriptJavaScript (with path extensions influence)
text/ecmascriptJavaScript (with path extensions influence)
application/x-javascriptJavaScript (with path extensions influence)
application/nodeJavaScript (with path extensions influence)
text/jsxJSX
text/tsxTSX
text/plainAttempt to determine that path extension, otherwise unknown
application/octet-streamAttempt to determine that path extension, otherwise unknown

Strict by default

Deno type checks TypeScript in strict mode by default, and the TypeScript core team recommends strict mode as a sensible default. This mode generally enables features of TypeScript that probably should have been there from the start, but as TypeScript continued to evolve, would be breaking changes for existing code.

Mixing JavaScript and TypeScript

By default, Deno does not type check JavaScript. This can be changed, and is discussed further in Configuring TypeScript in Deno. Deno does support JavaScript importing TypeScript and TypeScript importing JavaScript, in complex scenarios.

An important note though is that when type checking TypeScript, by default Deno will "read" all the JavaScript in order to be able to evaluate how it might have an impact on the TypeScript types. The type checker will do the best it can to figure out what the types are of the JavaScript you import into TypeScript, including reading any JSDoc comments. Details of this are discussed in detail in the Types and type declarations section.

Type resolution

One of the core design principles of Deno is to avoid non-standard module resolution, and this applies to type resolution as well. If you want to utilise JavaScript that has type definitions (e.g. a .d.ts file), you have to explicitly tell Deno about this. The details of how this is accomplished are covered in the Types and type declarations section.