2021-01-19 17:28:58 -05:00
|
|
|
|
## Types and Type Declarations
|
|
|
|
|
|
|
|
|
|
One of the design principles of Deno is no _magical_ resolution. When TypeScript
|
|
|
|
|
is type checking a file, it only cares about the types for the file, and the
|
|
|
|
|
`tsc` compiler has a lot of logic to try to resolve those types. By default, it
|
|
|
|
|
expects _ambiguous_ module specifiers with an extension, and will attempt to
|
|
|
|
|
look for the file under the `.ts` specifier, then `.d.ts`, and finally `.js`
|
|
|
|
|
(plus a whole other set of logic when the module resolution is set to `"node"`).
|
|
|
|
|
Deno deals with explicit specifiers.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
### Providing types when importing
|
|
|
|
|
|
|
|
|
|
If you are consuming a JavaScript module and you have either created types (a
|
|
|
|
|
`.d.ts` 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:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// @deno-types="./coolLib.d.ts"
|
|
|
|
|
import * as coolLib from "./coolLib.js";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
### Providing types when hosting
|
|
|
|
|
|
|
|
|
|
If you are in control of the source code of the module, or you are in control of
|
|
|
|
|
how the file is hosted on a web server, there are two ways to inform Deno of the
|
|
|
|
|
types for a given module, without requiring the importer to do anything special.
|
|
|
|
|
|
|
|
|
|
#### Using the triple-slash reference directive
|
|
|
|
|
|
2021-05-11 01:04:00 -04:00
|
|
|
|
Deno supports using the triple-slash reference `types` directive, which adopts
|
|
|
|
|
the reference comment used by TypeScript in TypeScript files to _include_ other
|
|
|
|
|
files and applies it only to JavaScript files.
|
2021-01-19 17:28:58 -05:00
|
|
|
|
|
2021-05-11 01:04:00 -04:00
|
|
|
|
For example, if I had created `coolLib.js` and along side of it I had created my
|
2021-01-19 17:28:58 -05:00
|
|
|
|
type definitions for my library in `coolLib.d.ts` I could do the following in
|
|
|
|
|
the `coolLib.js` file:
|
|
|
|
|
|
|
|
|
|
```js
|
2021-05-11 01:04:00 -04:00
|
|
|
|
/// <reference types="./coolLib.d.ts" />
|
2021-01-19 17:28:58 -05:00
|
|
|
|
|
|
|
|
|
// ... the rest of the JavaScript ...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2021-05-11 01:04:00 -04:00
|
|
|
|
> ℹ️ _Note_ this is a repurposed directive for TypeScript that only applies to
|
|
|
|
|
> JavaScript files. Using the triple-slash reference directive of `types` in a
|
|
|
|
|
> TypeScript file works under Deno as well, but has essentially the same
|
|
|
|
|
> behavior as the `path` directive.
|
|
|
|
|
|
2021-01-19 17:28:58 -05:00
|
|
|
|
#### 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 `https://example.com/coolLib.js` might look something like this:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
|
Content-Type: application/javascript; charset=UTF-8
|
|
|
|
|
Content-Length: 648
|
|
|
|
|
X-TypeScript-Types: ./coolLib.d.ts
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When seeing this header, Deno would attempt to retrieve
|
|
|
|
|
`https://example.com/coolLib.d.ts` and use that when type checking the original
|
|
|
|
|
module.
|
|
|
|
|
|
2021-06-21 17:18:32 -04:00
|
|
|
|
### Using ambient or global types
|
|
|
|
|
|
|
|
|
|
Overall it is better to use module/UMD type definitions with Deno, where a
|
|
|
|
|
module expressly imports the types it depends upon. Modular type definitions can
|
|
|
|
|
express
|
|
|
|
|
[augmentation of the global scope](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html)
|
|
|
|
|
via the `declare global` in the type definition. For example:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
declare global {
|
|
|
|
|
var AGlobalString: string;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This would make `AGlobalString` available in the global namespace when importing
|
|
|
|
|
the type definition.
|
|
|
|
|
|
|
|
|
|
In some cases though, when leveraging other existing type libraries, it may not
|
|
|
|
|
be possible to leverage modular type definitions. Therefore there are ways to
|
|
|
|
|
include arbitrary type definitions when type checking programmes.
|
|
|
|
|
|
|
|
|
|
#### Using a triple-slash directive
|
|
|
|
|
|
|
|
|
|
This option couples the type definitions to the code itself. By adding a
|
|
|
|
|
triple-slash `types` directive near the type of a module, type checking the file
|
|
|
|
|
will include the type definition. For example:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
/// <reference types="./types.d.ts" />
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The specifier provided is resolved just like any other specifier in Deno, which
|
|
|
|
|
means it requires an extension, and is relative to the module referencing it. It
|
|
|
|
|
can be a fully qualified URL as well:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
/// <reference types="https://deno.land/x/pkg@1.0.0/types.d.ts" />
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Using a `tsconfig.json` file
|
|
|
|
|
|
|
|
|
|
Another option is to use a `tsconfig.json` file that is configured to include
|
|
|
|
|
the type definitions, by supplying a `"types"` value to the `"compilerOptions"`.
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"compilerOptions": {
|
|
|
|
|
"types": [
|
|
|
|
|
"./types.d.ts",
|
|
|
|
|
"https://deno.land/x/pkg@1.0.0/types.d.ts",
|
|
|
|
|
"/Users/me/pkg/types.d.ts"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Like the triple-slash reference above, the specifier supplied in the `"types"`
|
|
|
|
|
array will be resolved like other specifiers in Deno. In the case of relative
|
|
|
|
|
specifiers, it will be resolved relative to the path to the `tsconfig.json`.
|
|
|
|
|
|
2021-06-08 21:52:27 -04:00
|
|
|
|
### Type Checking Web Workers
|
|
|
|
|
|
|
|
|
|
When Deno loads a TypeScript module in a web worker, it will automatically type
|
|
|
|
|
check the module and its dependencies against the Deno web worker library. This
|
|
|
|
|
can present a challenge in other contexts like `deno cache`, `deno bundle`, or
|
|
|
|
|
in editors. There are a couple of ways to instruct Deno to use the worker
|
|
|
|
|
libraries instead of the standard Deno libraries.
|
|
|
|
|
|
|
|
|
|
#### Using triple-slash directives
|
|
|
|
|
|
|
|
|
|
This option couples the library settings with the code itself. By adding the
|
|
|
|
|
following triple-slash directives near the top of the entry point file for the
|
|
|
|
|
worker script, Deno will now type check it as a Deno worker script, irrespective
|
|
|
|
|
of how the module is analyzed:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
/// <reference no-default-lib="true" />
|
|
|
|
|
/// <reference lib="deno.worker" />
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The first directive ensures that no other default libraries are used. If this is
|
|
|
|
|
omitted, you will get some conflicting type definitions, because Deno will try
|
|
|
|
|
to apply the standard Deno library as well. The second instructs Deno to apply
|
|
|
|
|
the built in Deno worker type definitions plus dependent libraries (like
|
|
|
|
|
`"esnext"`).
|
|
|
|
|
|
|
|
|
|
When you run a `deno cache` or `deno bundle` command or use an IDE which uses
|
|
|
|
|
the Deno language server, Deno should automatically detect these directives and
|
|
|
|
|
apply the correct libraries when type checking.
|
|
|
|
|
|
|
|
|
|
The one disadvantage of this, is that it makes the code less portable to other
|
|
|
|
|
non-Deno platforms like `tsc`, as it is only Deno which has the `"deno.worker"`
|
|
|
|
|
library built into it.
|
|
|
|
|
|
|
|
|
|
#### Using a `tsconfig.json` file
|
|
|
|
|
|
|
|
|
|
Another option is to use a `tsconfig.json` file that is configured to apply the
|
|
|
|
|
library files. A minimal file that would work would look something like this:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"compilerOptions": {
|
|
|
|
|
"target": "esnext",
|
|
|
|
|
"lib": ["deno.worker"]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Then when running a command on the command line, you would need to pass the
|
|
|
|
|
`--config tsconfig.json` argument, or if you are using an IDE which leverages
|
|
|
|
|
the Deno language server, set the `deno.config` setting.
|
|
|
|
|
|
|
|
|
|
If you also have non-worker scripts, you will either need to omit the `--config`
|
|
|
|
|
argument, or have one that is configured to meet the needs of your non-worker
|
|
|
|
|
scripts.
|
|
|
|
|
|
2021-01-19 17:28:58 -05:00
|
|
|
|
### Important points
|
|
|
|
|
|
|
|
|
|
#### 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.
|
|
|
|
|
|
|
|
|
|
To overcome this problem, some solution providers, like the
|
|
|
|
|
[Skypack CDN](https://www.skypack.dev/), will automatically bundle type
|
|
|
|
|
declarations just like they provide bundles of JavaScript as ESM.
|
|
|
|
|
|
|
|
|
|
#### Deno Friendly CDNs
|
|
|
|
|
|
|
|
|
|
There are CDNs which host JavaScript modules that integrate well with Deno.
|
|
|
|
|
|
|
|
|
|
- [Skypack.dev](https://docs.skypack.dev/skypack-cdn/code/deno) is a CDN which
|
|
|
|
|
provides type declarations (via the `X-TypeScript-Types` header) when you
|
|
|
|
|
append `?dts` as a query string to your remote module import statements. For
|
|
|
|
|
example:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import React from "https://cdn.skypack.dev/react?dts";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Behavior of JavaScript when type checking
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
When the module graph is built, and there is a need to type check the graph,
|
|
|
|
|
Deno starts up the TypeScript compiler and feeds it the names of the modules
|
|
|
|
|
that need to be potentially emitted as JavaScript. During that process, the
|
|
|
|
|
TypeScript compiler will request additional modules, and Deno will look at the
|
|
|
|
|
slots for the dependency, offering it the type slot if it is filled before
|
|
|
|
|
offering it the code 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.
|