This can cause a couple problems though. For example, let’s say I want to
consume a TypeScript file that has already been transpiled to JavaScript along
with a type definition file. So I have mod.js
and mod.d.ts
. If I try to
import mod.js
into Deno, it will only do what I ask it to do, and import
mod.js
, but that means my code won’t be as well type checked as if TypeScript
was considering the mod.d.ts
file in place of the mod.js
file.
In order to support this in Deno, Deno has two solutions, of which there is a variation of a solution to enhance support. The two main situations you come across would be:
- As the importer of a JavaScript module, I know what types should be applied to the module.
- As the supplier of the JavaScript module, I know what types should be applied to the module.
The latter case is the better case, meaning you as the provider or host of the module, everyone can consume it without having to figure out how to resolve the types for the JavaScript module, but when consuming modules that you may not have direct control over, the ability to do the former is also required.
If you are consuming a JavaScript module and you have either created types (a
file) or have otherwise obtained the types, you want to use, you can
instruct Deno to use that file when type checking instead of the JavaScript file
using the @deno-types
compiler hint. @deno-types
needs to be a single line
double slash comment, where when used impacts the next import or re-export
statement.
For example if I have a JavaScript modules coolLib.js
and I had a separate
coolLib.d.ts
file that I wanted to use, I would import it like this:
When type checking coolLib
and your usage of it in the file, the
coolLib.d.ts
types will be used instead of looking at the JavaScript file.
The pattern matching for the compiler hint is somewhat forgiving and will accept quoted and non-question values for the specifier as well as it accepts whitespace before and after the equals sign.
Using the triple-slash reference directive
Deno supports using the triple-slash reference directive, which adopts the reference comment used by TypeScript in TypeScript files to include other files and applies it to JavaScript files.
For example, if I had create coolLib.js
and along side of it I had created my
type definitions for my library in coolLib.d.ts
I could do the following in
the coolLib.js
file:
When Deno encounters this directive, it would resolve the ./coolLib.d.ts
file
and use that instead of the JavaScript file when TypeScript was type checking
the file, but still load the JavaScript file when running the program.
Using X-TypeScript-Types header
Similar to the triple-slash directive, Deno supports a header for remote modules that instructs Deno where to locate the types for a given module. For example, a response for might look something like this:
When seeing this header, Deno would attempt to retrieve
https://example.com/coolLib.d.ts
and use that when type checking the original
module.
Type declaration semantics
Type declaration files (.d.ts
files) follow the same semantics as other files
in Deno. This means that declaration files are assumed to be module declarations
(UMD declarations) and not ambient/global declarations. It is unpredictable
how Deno will handle ambient/global declarations.
In addition, if a type declaration imports something else, like another .d.ts
file, its resolution follow the normal import rules of Deno. For a lot of the
.d.ts
files that are generated and available on the web, they may not be
compatible with Deno.
Deno Friendly CDNs
There are CDNs which host JavaScript modules that integrate well with Deno.
If you import JavaScript into TypeScript in Deno and there are no types, even if
you have checkJs
set to false
(the default for Deno), the TypeScript
compiler will still access the JavaScript module and attempt to do some static
analysis on it, to at least try to determine the shape of the exports of that
module to validate the import in the TypeScript file.
This is usually never a problem when trying to import a “regular” ES module, but in some cases if the module has special packaging, or is a global UMD module, TypeScript’s analysis of the module can fail and cause misleading errors. The best thing to do in this situation is provide some form of types using one of the methods mention above.
Internals
While it isn’t required to understand how Deno works internally to be able to leverage TypeScript with Deno well, it can help to understand how it works.
Before any code is executed or compiled, Deno generates a module graph by parsing the root module, and then detecting all of its dependencies, and then retrieving and parsing those modules, recursively, until all the dependencies are retrieved.
For each dependency, there are two potential “slots” that are used. There is the
code slot and the type slot. As the module graph is filled out, if the module is
something that is or can be emitted to JavaScript, it fills the code slot, and
type only dependencies, like .d.ts
files fill the type slot.
This means when you import a .d.ts
module, or you use one of the solutions
above to provide alternative type modules for JavaScript code, that is what is
provided to TypeScript instead when resolving the module.