Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use type definitions from .d.ts file without importing

I am migrating a web-application from plain Javascript to Typescript, and am compiling all separate files into a single one using the --outFile compiler option and /// <reference path="..."/> directives.

This is nice, because I can split my code up into multiple files without having to worry about a browser supporting import.

The one library I use is color-js, and it has type definitions inside a file called color.d.ts.

To use it with plain Javascript, I do the following:

index.html:

[...]
<script src="scripts/color-js/color.js"></script>
<script src="main.js"></script> 
[...]

main.js/main.ts

let Color = net.brehaut.Color;
[...]

At runtime, this also works fine with Typescript, but during compilation, I get errors like these:

scripts/cue.ts(4,13): error TS2304: Cannot find name 'net'. 
scripts/cue.ts(25,18): error TS2304: Cannot find name 'Color'. 
scripts/cue.ts(26,16): error TS2304: Cannot find name 'Color'.
scripts/cue.ts(27,19): error TS2304: Cannot find name 'Color'. 
scripts/main.ts(839,52): error TS2304: Cannot find name 'Color'. 
scripts/main.ts(1019,20): error TS2304: Cannot find name 'Color'. 
scripts/main.ts(1022,16): error TS2304: Cannot find name 'Color'.

Is there a way to use the type definitions in color.d.ts only to define the type for compile-time, without importing it as a module?

As soon as I import it as one, I can't use --outFile anymore and I'd also have to use something like RequireJS, which I didn't get to work. Plain ES6 or ES5 imports aren't supported by my browser.

I thought maybe /// <reference types="..." would do that job, but that seems to be used for something else.

like image 254
iFreilicht Avatar asked May 16 '17 12:05

iFreilicht


People also ask

What is Types D ts file?

d. ts files are declaration files that contain only type information. These files don't produce . js outputs; they are only used for typechecking. We'll learn more about how to write our own declaration files later.

Where do I put TypeScript definitions?

DefinitelyTyped / @types TypeScript automatically finds type definitions under node_modules/@types , so there's no other step needed to get these types available in your program.

How does TypeScript find type definitions?

When loading a JavaScript module from “node_modules” TypeScript will look in “node_modules/@types” to see if there is a corresponding type definition for this file. That's where the “@types” scope is special.


2 Answers

My hope here is to provide some digestible context for what the compiler is complaining about, why, and how to fix it. Unfortunately, the module-importing restrictions you described may be a different problem for you to tackle (I think AMD and SystemJS would be your only options with --outFile, but see my transpilation note later on.)

You referenced the triple-slash directive in your question

/// <reference types="..."

which is the directive for telling the compiler about type dependencies inside TS declaration files (.d.ts) -- not quite what's needed, since we're writing TS and not a declaration file. We want to include a missing declaration file that already exists somewhere. Note that TypeScript tries really, really hard to resolve and include type declaration files automatically for the developer, but libraries like color-js which don't specify a types location in their package.json nor use a typings or @types convention, the compiler just isn't able to find it.

You likely don't want to ditch your intellisense benefits by declaring global any variables to get around this issue. There is a criminally under-documented object called paths that we can add to our tsconfig.json compiler options, which is how we inform the compiler of additional places to look for types instead of modifying auto-loading behavior like the typesRoot or types options. Here's an example tsconfig.json file that seems to work well:

{
    "compilerOptions": {
        "module": "system",
        "target": "es5",
        "allowJs": true,
        "outFile": "./dist/main.js",
        "rootDir": "./src",
        "baseUrl": "./",
        "paths": {
            "*": ["color-js", "./node_modules/color-js/color.d.ts"]
        }
    },
    "exclude": ["dist"]
}

What this paths entry tells the compiler is to resolve any imports for the string "color-js", instead of resolving the module to the JS file, grab color.d.ts as the desired module instead from the specified path.

Here's what an example TS (or JS!) file might look like:

import Color from "color-js";

let colorManager = Color();

let twenty = colorManager.setRed(20).getRed();

While this solution uses the import keyword, one thought is that since we're transpiling to an outfile (read: it's a single file!) using the above tsconfig.json, we won't have to worry about the import keyword making it into the resulting main.js.

Let's say this configuration doesn't meet your use case, and you still find yourself searching for a way to use the type declarations without importing the module. If there is truly no other option for you, using a hack like the any-typed variable is the last resort.

Most declaration files are all-or-nothing in this regard, because what's actually getting exported from the .d.ts file (color-js's included) behind the scenes is a function that can build instances of Color. That's what the let colorManager = Color(); line is all about, we're utilizing the exported builder in addition to the type information about what the function builds. As you noticed, at run-time we may still be fine, but as far as the compiler is concerned if we can't import the typed function and call it, the build is dead in the water.

like image 113
Hypaethral Avatar answered Oct 18 '22 04:10

Hypaethral


Typescript 3.8 adds this feature with the import type {} from '...' syntax:

import type only imports declarations to be used for type annotations and declarations. It always gets fully erased, so there’s no remnant of it at runtime. Similarly, export type only provides an export that can be used for type contexts, and is also erased from TypeScript’s output.

It’s important to note that classes have a value at runtime and a type at design-time, and the use is context-sensitive. When using import type to import a class, you can’t do things like extend from it.

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html

like image 26
Tilded Avatar answered Oct 18 '22 06:10

Tilded