diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-01-20 09:28:58 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-20 09:28:58 +1100 |
commit | 02c6a88d8af08d3e3ae79724d9fb12d4a0f8222f (patch) | |
tree | 7289cc9eaefb6119fd6635bc742ab25cded83929 /docs/typescript/types.md | |
parent | 973c33c8995f63da187daa6a434333315192b521 (diff) |
docs: improve manual around typescript (#8139)
Fixes #9054
Diffstat (limited to 'docs/typescript/types.md')
-rw-r--r-- | docs/typescript/types.md | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/docs/typescript/types.md b/docs/typescript/types.md new file mode 100644 index 000000000..f1da8fe2e --- /dev/null +++ b/docs/typescript/types.md @@ -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. |