Warm tip: This article is reproduced from serverfault.com, please click

Use type definitions from .d.ts file without importing

发布于 2017-05-16 12:46:47

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.

Questioner
iFreilicht
Viewed
0
Hypaethral 2018-11-01 17:39:02

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.