Use type definitions from .d.ts file without importing
Asked Answered
A

2

15

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.

Anatol answered 16/5, 2017 at 12:46 Comment(0)
S
11

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.

Scarce answered 1/11, 2018 at 9:39 Comment(0)
C
9

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

Casserole answered 30/11, 2020 at 4:15 Comment(4)
Wow, who would've thought? Three years after I asked the question, the solution has become a simple one-liner.Anatol
So close - but this adds an unwanted export {}; to the js filesAntique
@Antique Why is that undesirable?Adoree
Apologies @claudekennilol, it has been too long and I do not recall. I can only guess that back then I was working on using typescript in an ancient ASP.NET code base that did not have a require(). I ended up creating my own simple require() function... and it was worth it for the typescript!Antique

© 2022 - 2024 — McMap. All rights reserved.