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 .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, ignore // @deno-types=”./coolLib.d.ts” import * as coolLib from “./coolLib.js”;

    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:

    1. HTTP/1.1 200 OK
    2. Content-Type: application/javascript; charset=UTF-8
    3. Content-Length: 648

    When seeing this header, Deno would attempt to retrieve https://example.com/coolLib.d.ts and use that when type checking the original module.

    This would make 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, ignore ///

    1. The specifier provided is resolved just like any other specifier in Deno, which
    2. means it requires an extension, and is relative to the module referencing it. It
    3. can be a fully qualified URL as well:
    4. ```ts, ignore
    5. /// <reference types="https://deno.land/x/pkg@1.0.0/types.d.ts" />

    Using a configuration file

    Another option is to use a configuration file that is configured to include the type definitions, by supplying a "types" value to the "compilerOptions". For example:

    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 config file. Make sure to tell Deno to use this file by specifying --config=path/to/file flag.

    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, ignore /// ///

    1. The first directive ensures that no other default libraries are used. If this is
    2. omitted, you will get some conflicting type definitions, because Deno will try
    3. to apply the standard Deno library as well. The second instructs Deno to apply
    4. the built-in Deno worker type definitions plus dependent libraries (like
    5. `"esnext"`).
    6. When you run a `deno cache` or `deno bundle` command or use an IDE which uses
    7. the Deno language server, Deno should automatically detect these directives and
    8. apply the correct libraries when type checking.
    9. non-Deno platforms like `tsc`, as it is only Deno which has the `"deno.worker"`
    10. library built into it.
    11. #### Using a configuration file
    12. Another option is to use a configuration file that is configured to apply the
    13. library files. A minimal file that would work would look something like this:
    14. ```json
    15. {
    16. "compilerOptions": {
    17. "target": "esnext",
    18. "lib": ["deno.worker"]
    19. }
    20. }

    Then when running a command on the command line, you would need to pass the --config path/to/file argument, or if you are using an IDE which leverages the Deno language server, set the deno.config setting.

    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, 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 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:

    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 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.