1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

docs: improve manual around typescript (#8139)

Fixes #9054
This commit is contained in:
Kitson Kelly 2021-01-20 09:28:58 +11:00 committed by GitHub
parent 973c33c899
commit 02c6a88d8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 918 additions and 407 deletions

View file

@ -75,6 +75,7 @@ pub const IGNORED_COMPILER_OPTIONS: &[&str] = &[
"inlineSources",
"module",
"noEmitHelpers",
"noErrorTruncation",
"noLib",
"noResolve",
"outDir",

View file

@ -8,6 +8,5 @@ In this chapter we'll discuss:
- [Writing our own script](./getting_started/first_steps.md)
- [Command line interface](./getting_started/command_line_interface.md)
- [Understanding permissions](./getting_started/permissions.md)
- [Using Deno with TypeScript](./getting_started/typescript.md)
- [Using WebAssembly](./getting_started/webassembly.md)
- [Debugging your code](./getting_started/debugging_your_code.md)

View file

@ -1,183 +1,4 @@
## Using TypeScript
<!-- TODO(lucacasonato): text on 'just import .ts' -->
Deno supports both JavaScript and TypeScript as first class languages at
runtime. This means it requires fully qualified module names, including the
extension (or a server providing the correct media type). In addition, Deno has
no "magical" module resolution. Instead, imported modules are specified as files
(including extensions) or fully qualified URL imports. Typescript modules can be
directly imported. E.g.
```ts
import { Response } from "https://deno.land/std@$STD_VERSION/http/server.ts";
import { queue } from "./collections.ts";
```
### `--no-check` option
When using `deno run`, `deno test`, `deno cache`, or `deno bundle` you can
specify the `--no-check` flag to disable TypeScript type checking. This can
significantly reduce the time that program startup takes. This can be very
useful when type checking is provided by your editor and you want startup time
to be as fast as possible (for example when restarting the program automatically
with a file watcher).
To make the most of skipping type checks, `--no-check` transpiles each module in
isolation without using information from imported modules. This maximizes
potential for concurrency and incremental rebuilds. On the other hand, the
transpiler cannot know if `export { Foo } from "./foo.ts"` should be preserved
(in case `Foo` is a value) or removed (in case `Foo` is strictly a type). To
resolve such ambiguities, Deno enforces
[`isolatedModules`](https://www.typescriptlang.org/tsconfig#isolatedModules) on
all TS code. This means that `Foo` in the above example must be a value, and the
[`export type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-exports)
syntax must be used instead if `Foo` is a type.
Another consequence of `isolatedModules` is that the type-directed `const enum`
is treated like `enum`. The legacy `import =` and `export =` syntaxes are also
not supported by `--no-check`.
### Using external type definitions
The out of the box TypeScript compiler though relies on both extension-less
modules and the Node.js module resolution logic to apply types to JavaScript
modules.
In order to bridge this gap, Deno supports three ways of referencing type
definition files without having to resort to "magic" resolution.
#### Compiler hint
If you are importing a JavaScript module, and you know where the type definition
for that module is located, you can specify the type definition at import. This
takes the form of a compiler hint. Compiler hints inform Deno the location of
`.d.ts` files and the JavaScript code that is imported that they relate to. The
hint is `@deno-types` and when specified the value will be used in the compiler
instead of the JavaScript module. For example, if you had `foo.js`, but you know
that alongside of it was `foo.d.ts` which was the types for the file, the code
would look like this:
```ts
// @deno-types="./foo.d.ts"
import * as foo from "./foo.js";
```
The value follows the same resolution logic as importing a module, meaning the
file needs to have an extension and is relative to the current module. Remote
specifiers are also allowed.
The hint affects the next `import` statement (or `export ... from` statement)
where the value of the `@deno-types` will be substituted at compile time instead
of the specified module. Like in the above example, the Deno compiler will load
`./foo.d.ts` instead of `./foo.js`. Deno will still load `./foo.js` when it runs
the program.
#### Triple-slash reference directive in JavaScript files
If you are hosting modules which you want to be consumed by Deno, and you want
to inform Deno about the location of the type definitions, you can utilize a
triple-slash directive in the actual code. For example, if you have a JavaScript
module and you would like to provide Deno with the location of the type
definition which happens to be alongside that file, your JavaScript module named
`foo.js` might look like this:
```js
/// <reference types="./foo.d.ts" />
export const foo = "foo";
```
Deno will see this, and the compiler will use `foo.d.ts` when type-checking the
file, though `foo.js` will be loaded at runtime. The resolution of the value of
the directive follows the same resolution logic as importing a module, meaning
the file needs to have an extension and is relative to the current file. Remote
specifiers are also allowed.
#### X-TypeScript-Types custom header
If you are hosting modules which you want to be consumed by Deno, and you want
to inform Deno the location of the type definitions, you can use a custom HTTP
header of `X-TypeScript-Types` to inform Deno of the location of that file.
The header works in the same way as the triple-slash reference mentioned above,
it just means that the content of the JavaScript file itself does not need to be
modified, and the location of the type definitions can be determined by the
server itself.
**Not all type definitions are supported.**
Deno will use the compiler hint to load the indicated `.d.ts` files, but some
`.d.ts` files contain unsupported features. Specifically, some `.d.ts` files
expect to be able to load or reference type definitions from other packages
using the module resolution logic. For example a type reference directive to
include `node`, expecting to resolve to some path like
`./node_modules/@types/node/index.d.ts`. Since this depends on non-relative
"magical" resolution, Deno cannot resolve this.
**Why not use the triple-slash type reference in TypeScript files?**
The TypeScript compiler supports triple-slash directives, including a type
reference directive. If Deno used this, it would interfere with the behavior of
the TypeScript compiler. Deno only looks for the directive in JavaScript (and
JSX) files.
### Custom TypeScript Compiler Options
In the Deno ecosystem, all strict flags are enabled in order to comply with
TypeScript's ideal of being `strict` by default. However, in order to provide a
way to support customization a configuration file such as `tsconfig.json` might
be provided to Deno on program execution.
You need to explicitly tell Deno where to look for this configuration by setting
the `-c` (or `--config`) argument when executing your application.
```shell
deno run -c tsconfig.json mod.ts
```
Following are the currently allowed settings and their default values in Deno:
```json
{
"compilerOptions": {
"allowJs": false,
"allowUmdGlobalAccess": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"assumeChangesOnlyAffectDirectDependencies": false,
"checkJs": false,
"disableSizeLimit": false,
"generateCpuProfile": "profile.cpuprofile",
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"lib": [],
"noFallthroughCasesInSwitch": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitUseStrict": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveConstEnums": false,
"removeComments": false,
"resolveJsonModule": true,
"strict": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"suppressExcessPropertyErrors": false,
"suppressImplicitAnyIndexErrors": false,
"useDefineForClassFields": false
}
}
```
For documentation on allowed values and use cases please visit the
[typescript docs](https://www.typescriptlang.org/docs/handbook/compiler-options.html).
**Note**: Any options not listed above are either not supported by Deno or are
listed as deprecated/experimental in the TypeScript documentation.
> This section has been moved to
> [Using TypeScript Chapter](../typescript.md).

View file

@ -1,225 +1,4 @@
## Compiler APIs
> This API is unstable. Learn more about
> [unstable features](../runtime/stability.md).
Deno supports runtime access to the built-in TypeScript compiler. There are
three methods in the `Deno` namespace that provide this access.
### `Deno.compile()`
This works similar to `deno cache` in that it can fetch and cache the code,
compile it, but not run it. It takes up to three arguments, the `rootName`,
optionally `sources`, and optionally `options`. The `rootName` is the root
module which will be used to generate the resulting program. This is like the
module name you would pass on the command line in
`deno run --reload example.ts`. The `sources` is a hash where the key is the
fully qualified module name, and the value is the text source of the module. If
`sources` is passed, Deno will resolve all the modules from within that hash and
not attempt to resolve them outside of Deno. If `sources` are not provided, Deno
will resolve modules as if the root module had been passed on the command line.
Deno will also cache any of these resources. All resolved resources are treated
as dynamic imports and require read or net permissions depending on if they're
local or remote. The `options` argument is a set of options of type
`Deno.CompilerOptions`, which is a subset of the TypeScript compiler options
containing the ones supported by Deno.
The method resolves with a tuple. The first argument contains any diagnostics
(syntax or type errors) related to the code. The second argument is a map where
the keys are the output filenames and the values are the content.
An example of providing sources:
```ts
const [diagnostics, emitMap] = await Deno.compile("/foo.ts", {
"/foo.ts": `import * as bar from "./bar.ts";\nconsole.log(bar);\n`,
"/bar.ts": `export const bar = "bar";\n`,
});
assert(diagnostics == null); // ensuring no diagnostics are returned
console.log(emitMap);
```
We would expect map to contain 4 "files", named `/foo.js.map`, `/foo.js`,
`/bar.js.map`, and `/bar.js`.
When not supplying resources, you can use local or remote modules, just like you
could do on the command line. So you could do something like this:
```ts
const [diagnostics, emitMap] = await Deno.compile(
"https://deno.land/std@$STD_VERSION/examples/welcome.ts",
);
```
In this case `emitMap` will contain a `console.log()` statement.
### `Deno.bundle()`
This works a lot like `deno bundle` does on the command line. It is also like
`Deno.compile()`, except instead of returning a map of files, it returns a
single string, which is a self-contained JavaScript ES module which will include
all of the code that was provided or resolved as well as exports of all the
exports of the root module that was provided. It takes up to three arguments,
the `rootName`, optionally `sources`, and optionally `options`. The `rootName`
is the root module which will be used to generate the resulting program. This is
like module name you would pass on the command line in `deno bundle example.ts`.
The `sources` is a hash where the key is the fully qualified module name, and
the value is the text source of the module. If `sources` is passed, Deno will
resolve all the modules from within that hash and not attempt to resolve them
outside of Deno. If `sources` are not provided, Deno will resolve modules as if
the root module had been passed on the command line. All resolved resources are
treated as dynamic imports and require read or net permissions depending if
they're local or remote. Deno will also cache any of these resources. The
`options` argument is a set of options of type `Deno.CompilerOptions`, which is
a subset of the TypeScript compiler options containing the ones supported by
Deno.
An example of providing sources:
```ts
const [diagnostics, emit] = await Deno.bundle("/foo.ts", {
"/foo.ts": `import * as bar from "./bar.ts";\nconsole.log(bar);\n`,
"/bar.ts": `export const bar = "bar";\n`,
});
assert(diagnostics == null); // ensuring no diagnostics are returned
console.log(emit);
```
We would expect `emit` to be the text for an ES module, which would contain the
output sources for both modules.
When not supplying resources, you can use local or remote modules, just like you
could do on the command line. So you could do something like this:
```ts
const [diagnostics, emit] = await Deno.bundle(
"https://deno.land/std@$STD_VERSION/http/server.ts",
);
```
In this case `emit` will be a self contained JavaScript ES module with all of
its dependencies resolved and exporting the same exports as the source module.
### `Deno.transpileOnly()`
This is based off of the TypeScript function `transpileModule()`. All this does
is "erase" any types from the modules and emit JavaScript. There is no type
checking and no resolution of dependencies. It accepts up to two arguments, the
first is a hash where the key is the module name and the value is the content.
The only purpose of the module name is when putting information into a source
map, of what the source file name was. The second argument contains optional
`options` of the type `Deno.CompilerOptions`. The function resolves with a map
where the key is the source module name supplied, and the value is an object
with a property of `source` and optionally `map`. The first is the output
contents of the module. The `map` property is the source map. Source maps are
provided by default, but can be turned off via the `options` argument.
An example:
```ts
const result = await Deno.transpileOnly({
"/foo.ts": `enum Foo { Foo, Bar, Baz };\n`,
});
console.log(result["/foo.ts"].source);
console.log(result["/foo.ts"].map);
```
We would expect the `enum` would be rewritten to an IIFE which constructs the
enumerable, and the map to be defined.
### Referencing TypeScript library files
When you use `deno run`, or other Deno commands which type check TypeScript,
that code is evaluated against custom libraries which describe the environment
that Deno supports. By default, the compiler runtime APIs which type check
TypeScript also use these libraries (`Deno.compile()` and `Deno.bundle()`).
But if you want to compile or bundle TypeScript for some other runtime, you may
want to override the default libraries. To do this, the runtime APIs support the
`lib` property in the compiler options. For example, if you had TypeScript code
that is destined for the browser, you would want to use the TypeScript `"dom"`
library:
```ts
const [errors, emitted] = await Deno.compile(
"main.ts",
{
"main.ts": `document.getElementById("foo");\n`,
},
{
lib: ["dom", "esnext"],
},
);
```
For a list of all the libraries that TypeScript supports, see the
[`lib` compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
documentation.
**Don't forget to include the JavaScript library**
Just like `tsc`, when you supply a `lib` compiler option, it overrides the
default ones, which means that the basic JavaScript library won't be included
and you should include the one that best represents your target runtime (e.g.
`es5`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020` or `esnext`).
#### Including the `Deno` namespace
In addition to the libraries that are provided by TypeScript, there are four
libraries that are built into Deno that can be referenced:
- `deno.ns` - Provides the `Deno` namespace.
- `deno.shared_globals` - Provides global interfaces and variables which Deno
supports at runtime that are then exposed by the final runtime library.
- `deno.window` - Exposes the global variables plus the Deno namespace that are
available in the Deno main worker and is the default for the runtime compiler
APIs.
- `deno.worker` - Exposes the global variables that are available in workers
under Deno.
So to add the Deno namespace to a compilation, you would include the `deno.ns`
lib in the array. For example:
```ts
const [errors, emitted] = await Deno.compile(
"main.ts",
{
"main.ts": `document.getElementById("foo");\n`,
},
{
lib: ["dom", "esnext", "deno.ns"],
},
);
```
**Note** that the Deno namespace expects a runtime environment that is at least
ES2018 or later. This means if you use a lib "lower" than ES2018 you will get
errors logged as part of the compilation.
#### Using the triple slash reference
You do not have to specify the `lib` in the compiler options. Deno also supports
[the triple-slash reference to a lib](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-lib-)
which can be embedded in the contents of the file. For example, if you have a
`main.ts` like:
```ts
/// <reference lib="dom" />
document.getElementById("foo");
```
It would compile without errors like this:
```ts
const [errors, emitted] = await Deno.compile("./main.ts", undefined, {
lib: ["esnext"],
});
```
**Note** that the `dom` library conflicts with some of the default globals that
are defined in the default type library for Deno. To avoid this, you need to
specify a `lib` option in the compiler options to the runtime compiler APIs.
> This section has been moved to
> [TypeScript Runtime APIs](../typescript/runtime.md).

View file

@ -10,7 +10,6 @@
"first_steps": "First steps",
"command_line_interface": "Command line interface",
"permissions": "Permissions",
"typescript": "Using TypeScript",
"webassembly": "Using WebAssembly",
"debugging_your_code": "Debugging your code"
}
@ -21,7 +20,6 @@
"stability": "Stability",
"program_lifecycle": "Program lifecycle",
"permission_apis": "Permission APIs",
"compiler_apis": "Compiler APIs",
"web_platform_apis": "Web Platform APIs",
"location_api": "Location API",
"workers": "Workers"
@ -36,6 +34,17 @@
"import_maps": "Import maps"
}
},
"typescript": {
"name": "Using TypeScript",
"children": {
"overview": "Overview",
"configuration": "Configuration",
"types": "Types and type declarations",
"migration": "Migrating to/from JavaScript",
"runtime": "Runtime compiler APIs",
"faqs": "Frequently asked questions"
}
},
"standard_library": {
"name": "Standard library"
},

10
docs/typescript.md Normal file
View file

@ -0,0 +1,10 @@
# Using TypeScript
In this chapter we will discuss:
- [Overview of TypeScript in Deno](./typescript/overview.md)
- [Configuring TypeScript in Deno](./typescript/configuration.md)
- [Types and Type Declarations](./typescript/types.md)
- [Migrating to/from JavaScript](./typescript/migration.md)
- [Runtime compiler APIs](./typescript/runtime.md)
- [FAQs about TypeScript in Deno](./typescript/faqs.md)

View file

@ -0,0 +1,110 @@
## Configuring TypeScript in Deno
TypeScript comes with a load of different options that can be configured, but
Deno strives to make it easy to use TypeScript with Deno. Lots of different
options frustrates that goal. To make things easier, Deno configures TypeScript
to "just work" and shouldn't require additional configuration.
That being said, Deno does support using a TypeScript configuration file, though
like the rest of Deno, the detection and use of use of a configuration file is
not automatic. To use a TypeScript configuration file with Deno, you have to
provide a path on the command line. For example:
```
> deno run --config ./tsconfig.json main.ts
```
> ⚠️ Do consider though that if you are creating libraries that require a
> configuration file, all of the consumers of your modules will require that
> configuration file too if you distribute your modules as TypeScript. In
> addition, there could be settings you do in the configuration file that make
> other TypeScript modules incompatible. Honestly it is best to use the Deno
> defaults and to think long and hard about using a configuration file.
### How Deno uses a configuration file
Deno does not process a TypeScript configuration file like `tsc` does, as there
are lots of parts of a TypeScript configuration file that are meaningless in a
Deno context or would cause Deno to not function properly if they were applied.
Deno only looks at the `compilerOptions` section of a configuration file, and
even then it only considers certain compiler options, with the rest being
ignored.
Here is a table of compiler options that can be changed, their default in Deno
and any other notes about that option:
| Option | Default | Notes |
| -------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowJs` | `true` | This almost never needs to be changed |
| `allowUnreachableCode` | `false` | |
| `allowUnusedLabels` | `false` | |
| `checkJs` | `false` | If `true` causes TypeScript to type check JavaScript |
| `experimentalDecorators` | `true` | We enable these by default as they are already opt-in in the code and when we skip type checking, the Rust based emitter has them on by default. We strongly discourage the use of legacy decorators, as they are incompatible with the future decorators standard in JavaScript |
| `jsx` | `"react"` | |
| `jsxFactory` | `"React.createElement"` | |
| `jsxFragmentFactory` | `"React.Fragment"` | |
| `keysofStringsOnly` | `false` | |
| `lib` | `[ "deno.window" ]` | The default for this varies based on other settings in Deno. If it is supplied, it overrides the default. See below for more information. |
| `noFallthroughCasesInSwitch` | `false` | |
| `noImplicitAny` | `true` | |
| `noImplicitReturns` | `false` | |
| `noImplicitThis` | `true` | |
| `noImplicitUseStrict` | `true` | |
| `noStrictGenericChecks` | `false` | |
| `noUnusedLocals` | `false` | |
| `noUnusedParameters` | `false` | |
| `reactNamespace` | `React` | |
| `strict` | `true` | |
| `strictBindApply` | `true` | |
| `strictFunctionTypes` | `true` | |
| `strictPropertyInitialization` | `true` | |
| `strictNullChecks` | `true` | |
| `suppressExcessPropertyErrors` | `false` | |
| `suppressImplicitAnyIndexErrors` | `false` | |
For a full list of compiler options and how they affect TypeScript, please refer
to the
[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
### What an implied tsconfig.json looks like
It is impossible to get `tsc` to behave like Deno. It is also difficult to get
the TypeScript language service to behave like Deno. This is why we have built a
language service directly into Deno. That being said, it can be useful to
understand what is implied.
If you were to write a `tsconfig.json` for Deno, it would look something like
this:
```json
{
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"inlineSourceMap": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["deno.window"],
"module": "esnext",
"strict": true,
"target": "esnext"
}
}
```
You can't copy paste this into a `tsconfig.json` and get it to work,
specifically because of the built in type libraries that are custom to Deno
which are provided to the TypeScript compiler. This can somewhat be mocked by
running `deno types` on the command line and piping the output to a file and
including that in the files as part of the program, removing the `"lib"` option,
and setting the `"noLib"` option to `true`.
If you use the `--unstable` flag, Deno will change the `"lib"` option to
`[ "deno.window", "deno.unstable" ]`. If you are trying to load a worker, that
is type checked with `"deno.worker"` instead of `"deno.window"`.
### Using the "lib" property
[TBC]

116
docs/typescript/faqs.md Normal file
View file

@ -0,0 +1,116 @@
## FAQs about TypeScript in Deno
### Can I use TypeScript not written for Deno?
Maybe. That is the best answer, we are afraid. For lots of reasons, Deno has
chosen to have fully qualified module specifiers. In part this is because it
treats TypeScript as a first class language. Also, Deno uses explicit module
resolution, with no _magic_. This is effectively the same way browsers
themselves work, thought they don't obviously support TypeScript directly. If
the TypeScript modules use imports that don't have these design decisions in
mind, they may not work under Deno.
Also, in recent versions of Deno (starting with 1.5), we have started to use a
Rust library to do transformations of TypeScript to JavaScript in certain
scenarios. Because of this, there are certain situations in TypeScript where
type information is required, and therefore those are not supported under Deno.
If you are using `tsc` as stand-alone, the setting to use is `"isolatedModules"`
and setting it to `true` to help ensure that your code can be properly handled
by Deno.
One of the ways to deal with the extension and the lack of _magical_ resolution
is to use [import maps](../linking_to_external_code/import_maps.md) which would
allow you to specify "packages" of bare specifiers which then Deno could resolve
and load.
### What version(s) of TypeScript does Deno support?
Deno is built with a specific version of TypeScript. To find out what this is,
type the following on the command line:
```shell
> deno --version
```
The TypeScript version (along with the version of Deno and v8) will be printed.
Deno tries to keep up to date with general releases of TypeScript, providing
them in the next patch or minor release of Deno.
### There was a breaking change in the version of TypeScript that Deno uses, why did you break my program?
We do not consider changes in behavior or breaking changes in TypeScript
releases as breaking changes for Deno. TypeScript is a generally mature language
and breaking changes in TypeScript are almost always "good things" making code
more sound, and it is best that we all keep our code sound. If there is a
blocking change in the version of TypeScript and it isn't suitable to use an
older release of Deno until the problem can be resolved, then you should be able
to use `--no-check` to skip type checking all together.
In addition you can utilize `@ts-ignore` to _ignore_ a specific error in code
that you control. You can also replace whole dependencies, using
[import maps](../linking_to_external_code/import_maps), for situations where a
dependency of a dependency isn't being maintained or has some sort of breaking
change you want to bypass while waiting for it to be updated.
### Why are you forcing me to use isolated modules, why can't I use const enums with Deno, why do I need to do export type?
As of Deno 1.5 we defaulted to _isolatedModules_ to `true` and in Deno 1.6 we
removed the options to set it back to `false` via a configuration file. The
_isolatedModules_ option forces the TypeScript compiler to check and emit
TypeScript as if each module would stand on its own. TypeScript has a few _type
directed emits_ in the language at the moment. While not allowing type directed
emits into the language was a design goal for TypeScript, it has happened
anyways. This means that the TypeScript compiler needs to understand the
erasable types in the code to determine what to emit, which when you are trying
to make a fully erasable type system on top of JavaScript, that becomes a
problem.
When people started transpiling TypeScript without `tsc`, these type directed
emits became a problem, since the likes of Babel simply try to erase the types
without needing to understand the types to direct the emit. In the internals of
Deno we have started to use a Rust based emitter which allows us to optionally
skip type checking and generates the bundles for things like `deno bundle`. Like
all transpilers, it doesn't care about the types, it just tries to erase them.
This means in certain situations we cannot support those type directed emits.
So instead of trying to get every user to understand when and how we could
support the type directed emits, we made the decision to disable the use of them
by forcing the _isolatedModules_ option to `true`. This means that even when we
are using the TypeScript compiler to emit the code, it will follow the same
"rules" that the Rust based emitter follows.
This means that certain language features are not supportable. Those features
are:
- Re-exporting of types is ambigious and requires to know if the source module
is exporting runtime code or just type information. Therefore, it is
recommended that you use `import type` and `export type` for type only imports
and exports. This will help ensure that when the code is emitted, that all the
types are erased.
- `const enum` is not supported. `const enum`s require type information to
direct the emit, as `const enum`s get written out as hard coded values.
Especially when `const enum`s get exported, they are a type system only
construct.
- `export =` and `import =` are legacy TypeScript syntax which we do not
support.
- Only `declare namespace` is support. Runtime `namespace` is legacy TypeScript
syntax that is not supported.
### Why don't you support language service plugins or transformer plugins?
While `tsc` supports language service plugins, Deno does not. Deno does not
always use the built in TypeScript compiler to do what it does, and the
complexity of adding support for a language service plugin is not feasible.
TypeScript does not support emitter plugins, but there are a few community
projects which _hack_ emitter plugins into TypeScript. First, we wouldn't want
to support something that TypeScript doesn't support, plus we do not always use
the TypeScript compiler for the emit, which would mean we would need to ensure
we supported it in all modes, and the other emitter is written in Rust, meaning
that any emitter plugin for TypeScript wouldn't be available for the Rust
emitter.
The TypeScript in Deno isn't intended to be a fully flexible TypeScript
compiler. Its main purpose is to ensure that TypeScript and JavaScript can run
under Deno. The secondary ability to do TypeScript and JavaScript emitting via
the runtime API `Deno.emit()` is intended to be simple and straight forward and
support a certain set of use cases.

View file

@ -0,0 +1,71 @@
## Migrating to and from JavaScript
One of the advantages of Deno is that it treats TypeScript and JavaScript pretty
equally. This might mean that transitioning from JavaScript to TypeScript or
even from TypeScript to JavaScript is something you want to accomplish. There
are several features of Deno that can help with this.
### Type checking JavaScript
You might have some JavaScript that you would like to ensure is more type sound
but you don't want to go through a process of adding type annotations
everywhere.
Deno supports using the TypeScript type checker to type check JavaScript. You
can mark any individual file by adding the check JavaScript pragma to the file:
```js
// @ts-check
```
This will cause the type checker to infer type information about the JavaScript
code and raise any issues as diagnostic issues.
These can be turned on for all JavaScript files in a program by providing a
configuration file with the check JS option enabled:
```json
{
"compilerOptions": {
"checkJs": true
}
}
```
And setting the `--config` option on the command line.
### Using JSDoc in JavaScript
If you are type checking JavaScript, or even importing JavaScript into
TypeScript you can use JSDoc in JavaScript to express more types information
than can just be inferred from the code itself. Deno supports this without any
additional configuration, you simply need to annotate the code in line with the
supported
[TypeScript JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html).
For example to set the type of an array:
```js
/** @type {string[]} */
const a = [];
```
### Skipping type checking
You might have TypeScript code that you are experimenting with, where the syntax
is valid but not fully type safe. You can always bypass type checking for a
whole program by passing the `--no-check`.
You can also skip whole files being type checked, including JavaScript if you
have check JS enabled, by using the no-check pragma:
```js
// @ts-nocheck
```
### Just renaming JS files to TS files
While this might work in some cases, it has some severe limits in Deno. This is
because Deno, by default, runs type checking in what is called _strict mode_.
This means a lot of unclear or ambiguous situations where are not caught in
non-strict mode will result in diagnostics being generated, and JavaScript is
nothing but unclear and ambiguous when it comes to types.

159
docs/typescript/overview.md Normal file
View file

@ -0,0 +1,159 @@
## 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 Web Assembly, 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 Web Assembly goes through a similar translation, in that Web
Assembly 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 Web Assembly.
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](https://github.com/microsoft/TypeScript), which we build
into Deno, and a Rust library called [swc](https://swc.rs/). 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`:
```shell
> 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".
In Deno we handle TypeScript in two major ways. We can type check TypeScript,
the default, or you can opt into skipping that checking using the `--no-check`
flag. For example if you had a program you wanted to run, normally you would do
something like this:
```
deno run --allow-net my_server.ts
```
But if you wanted to skip the type checking, you would do something like this:
```
deno run --allow-net --no-check my_server.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
optimise the type checking, but it still comes at a cost. If you just want to
hack at some code, or if you are working in an IDE which is type checking your
code as you author it, using `--no-check` can certainly speed up the process of
running TypeScript in Deno.
### 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 fully on the extension. When the extension
is absent in a local file, it is assumed to be JavaScript.
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 Type | How File is Handled |
| -------------------------- | ----------------------------------------------------------- |
| `application/typescript` | TypeScript (with path extension influence) |
| `text/typescript` | TypeScript (with path extension influence) |
| `video/vnd.dlna.mpeg-tts` | TypeScript (with path extension influence) |
| `video/mp2t` | TypeScript (with path extension influence) |
| `application/x-typescript` | TypeScript (with path extension influence) |
| `application/javascript` | JavaScript (with path extensions influence) |
| `text/javascript` | JavaScript (with path extensions influence) |
| `application/ecmascript` | JavaScript (with path extensions influence) |
| `text/ecmascript` | JavaScript (with path extensions influence) |
| `application/x-javascript` | JavaScript (with path extensions influence) |
| `application/node` | JavaScript (with path extensions influence) |
| `text/jsx` | JSX |
| `text/tsx` | TSX |
| `text/plain` | Attempt to determine that path extension, otherwise unknown |
| `application/octet-stream` | Attempt 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](./configuration.md). Deno
does support JavaScript importing TypeScript and TypeScript import 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](./types.md) section.
### Diagnostics are terminal
While `tsc` by default will still emit JavaScript when run while encountering
diagnostic (type checking) issues, Deno currently treats them as terminal. It
will halt on these warnings, not cache any of the emitted files, and exit the
process.
In order to avoid this, you will either need to resolve the issue, utilise the
`// @ts-ignore` or `// @ts-expect-error` pragmas, or utilise `--no-check` to
bypass type checking all together.
### Type resolution
One of the core design principles of Deno is to avoid "magical" 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](./types.md) section.

268
docs/typescript/runtime.md Normal file
View file

@ -0,0 +1,268 @@
## Runtime compiler APIs
> ⚠️ The runtime compiler API is unstable (and requires the `--unstable` flag to
> be used to enable it).
The runtime compiler API allows access to the internals of Deno to be able to
type check, transpile and bundle JavaScript and TypeScript. As of Deno 1.7,
several disparate APIs we consolidated into a single API, `Deno.emit()`.
### Deno.emit()
The API is defined in the `Deno` namespace as:
```ts
function emit(
rootSpecifier: string | URL,
options?: EmitOptions,
): Promise<EmitResult>;
```
The emit options are defined in the `Deno` namespace as:
```ts
interface EmitOptions {
/** Indicate that the source code should be emitted to a single file
* JavaScript bundle that is an ES module (`"esm"`). */
bundle?: "esm";
/** If `true` then the sources will be typed checked, returning any
* diagnostic errors in the result. If `false` type checking will be
* skipped. Defaults to `true`.
*
* *Note* by default, only TypeScript will be type checked, just like on
* the command line. Use the `compilerOptions` options of `checkJs` to
* enable type checking of JavaScript. */
check?: boolean;
/** A set of options that are aligned to TypeScript compiler options that
* are supported by Deno. */
compilerOptions?: CompilerOptions;
/** An [import-map](https://deno.land/manual/linking_to_external_code/import_maps#import-maps)
* which will be applied to the imports. */
importMap?: ImportMap;
/** An absolute path to an [import-map](https://deno.land/manual/linking_to_external_code/import_maps#import-maps).
* Required to be specified if an `importMap` is specified to be able to
* determine resolution of relative paths. If a `importMap` is not
* specified, then it will assumed the file path points to an import map on
* disk and will be attempted to be loaded based on current runtime
* permissions.
*/
importMapPath?: string;
/** A record of sources to use when doing the emit. If provided, Deno will
* use these sources instead of trying to resolve the modules externally. */
sources?: Record<string, string>;
}
```
The emit result is defined in the `Deno` namespace as:
```ts
interface EmitResult {
/** Diagnostic messages returned from the type checker (`tsc`). */
diagnostics: Diagnostic[];
/** Any emitted files. If bundled, then the JavaScript will have the
* key of `deno:///bundle.js` with an optional map (based on
* `compilerOptions`) in `deno:///bundle.js.map`. */
files: Record<string, string>;
/** An optional array of any compiler options that were ignored by Deno. */
ignoredOptions?: string[];
/** An array of internal statistics related to the emit, for diagnostic
* purposes. */
stats: Array<[string, number]>;
}
```
The API is designed to support several use cases, which are described in the
sections below.
### Using external sources
Using external sources, both local and remote, `Deno.emit()` can behave like
`deno cache` does on the command line, resolving those external dependencies,
type checking those dependencies, and providing an emitted output.
By default, `Deno.emit()` will utilise external resources. The _rootSpecifier_
supplied as the first argument will determine what module will be used as the
root. The root module is similar to what you would provide on the command line.
For example if you did:
```
> deno run mod.ts
```
You could do something similar with `Deno.emit()`:
```ts
try {
const { files } = await Deno.emit("mod.ts");
for (const [fileName, text] of Object.entries(files)) {
console.log(`emitted ${fileName} with a length of ${text.length}`);
}
} catch (e) {
// something went wrong, inspect `e` to determine
}
```
`Deno.emit()` will use the same on disk cache for remote modules that the
standard CLI does, and it inherits the permissions and cache options of the
process that executes it.
If the _rootSpecifier_ is a relative path, then the current working directory of
the Deno process will be used to resolve the specifier. (Not relative to the
current module!)
The _rootSpecifier_ can be a string file path, a string URL, or a URL.
`Deno.emit()` supports the same protocols for URLs that Deno supports, which are
currently `file`, `http`, `https`, and `data`.
### Providing sources
Instead of resolving modules externally, you can provide `Deno.emit()` with the
sources directly. This is especially useful for a server to be able to provide
_on demand_ compiling of code supplied by a user, where the Deno process has
collected all the code it wants to emit.
The sources are passed in the _sources_ property of the `Deno.emit()` _options_
argument:
```ts
const { files } = await Deno.emit("/mod.ts", {
sources: {
"/mod.ts": `import * as a from "./a.ts";\nconsole.log(a);\n`,
"/a.ts": `export const a: Record<string, string> = {};\n`,
},
});
```
When sources are provided, Deno will no longer look externally and will try to
resolve all modules from within the map of sources provided, though the module
resolution follow the same rules as if the modules were external. For example
all module specifiers need their full filename. Also, because there are no media
types, if you are providing remote URLs in the sources, the path should end with
the appropriate extension, so that Deno can determine how to handle the file.
### Type checking and emitting
By default, `Deno.emit()` will type check any TypeScript (and TSX) it
encounters, just like on the command line. It will also attempt to transpile
JSX, but will leave JavaScript "alone". This behavior can be changed by changing
the compiler options. For example if you wanted Deno to type check your
JavaScript as well, you could set the _checkJs_ option to `true` in the compiler
options:
```ts
const { files, diagnostics } = await Deno.emit("./mod.js", {
compilerOptions: {
checkJs: true,
},
});
```
The `Deno.emit()` result provides any diagnostic messages about the code
supplied. On the command line, any diagnostic messages get logged to stderr and
the Deno process terminates, but with `Deno.emit()` they are returned to the
caller.
Typically you will want to check if there are any diagnostics and handle them
appropriately. You can introspect the diagnostics individually, but there is a
handy formatting function available to make it easier to potentially log the
diagnostics to the console for the user called `Deno.formatDiagnostics()`:
```ts
const { files, diagnostics } = await Deno.emit("./mod.ts");
if (diagnostics.length) {
// there is something that impacted the emit
console.warn(Deno.formatDiagnostics(diagnostics));
}
```
### Bundling
`Deno.emit()` is also capable of providing output similar to `deno bundle` on
the command line. This is enabled by setting the _bundle_ option to `"esm"`.
(Currently Deno only supports bundling as a single file ES module, but there are
plans to add support for an IIFE bundle format as well):
```ts
const { files, diagnostics } = await Deno.emit("./mod.ts", {
bundle: "esm",
});
```
The _files_ of the result will contain a single key named `deno:///bundle.js` of
which the value with be the resulting bundle.
> ⚠️ Just like with `deno bundle`, the bundle will not include things like
> dynamic imports or worker scripts, and those would be expected to be resolved
> and available when the code is run.
### Import maps
`Deno.emit()` supports import maps as well, just like on the command line. This
is a really powerful feature that can be used even more effectively to emit and
bundle code.
Because of the way import maps work, when using with `Deno.emit()` you also have
to supply an absolute URL for the import map. This allows Deno to resolve any
relative URLs specified in the import map. This needs to be supplied even if the
import map doesn't contain any relative URLs. The URL does not need to really
exist, it is just feed to the API.
An example might be that I want to use a bare specifier to load a special
version of _lodash_ I am using with my project. I could do the following:
```ts
const { files } = await Deno.emit("mod.ts", {
bundle: "esm",
importMap: {
imports: {
"lodash": "https://deno.land/x/lodash",
},
},
importMapPath: "file:///import-map.json",
});
```
> ⚠️ If you are not bundling your code, the emitted code specifiers do not get
> rewritten, that means that whatever process will consume the code, Deno or a
> browser for example, would need to support import maps and have that map
> available at runtime.
### Skip type checking/transpiling only
`Deno.emit()` supports skipping type checking similar to the `--no-check` flag
on the command line. This is accomplished by setting the _check_ property to
`false`:
```ts
const { files } = await Deno.emit("./mod.ts", {
check: false,
});
```
Setting _check_ to `false` will instruct Deno to not utilise the TypeScript
compiler to type check the code and emit it, instead only transpiling the code
from within Deno. This can be significantly quicker than doing the full type
checking.
### Compiler options
`Deno.emit()` support quite a few compiler options that can impact how code is
type checked and emitted. They are similar to the options supported by a
`tsconfig.json` in the `compilerOptions` section, but there are several options
that are not supported. This is because they are either meaningless in Deno our
would cause Deno to not be able to work properly. The defaults for `Deno.emit()`
are the same defaults that are on the command line. The options are
[documented here](https://doc.deno.land/builtin/unstable#Deno.CompilerOptions)
along with their default values and are built into the Deno types.
If you are type checking your code, the compiler options will be type checked
for you, but if for some reason you are either dynamically providing the
compiler options or are not type checking, then the result of `Deno.emit()` will
provide you with an array of _ignoredOptions_ if there are any.
> ⚠️ we have only tried to disable/remove options that we know won't work, that
> does not mean we extensively test all options in all configurations under
> `Deno.emit()`. You may find that some behaviors do not match what you can get
> from `tsc` or are otherwise incompatible. If you do find something that
> doesn't work, please do feel free to raise an issue.

168
docs/typescript/types.md Normal file
View file

@ -0,0 +1,168 @@
## 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
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:
```js
/// <reference path="./coolLib.d.ts" />
// ... 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.
#### 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.
### 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.