Namespace Imports in TypeScript

August 24th 2018 TypeScript Ionic 2/3

I've written about TypeScript imports on my blog before, but as I recently learned, I haven't covered all the cases.

It all started when I wanted to use the Google Maps JavaScript API in my Ionic application. Since there doesn't seem to be an official NPM package available, I referenced the script in my index.html directly:

<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY">
</script>

To get IntelliSense for the library in Visual Studio Code, I had to install the Google Maps type definitions as well:

npm install --save @types/googlemaps

In the editor now everything looked fine. I could reference any types from the Google Maps API from my code, e.g.:

let distanceMatrix = new google.maps.DistanceMatrixService();

However, the project failed to build:

Cannot find name 'google'.

I was stuck until I found an excellent resource on Stack Overflow. It still took me a while to get everything working, though.

I started by adding imports to my source code file.

  • I first managed to fix the build with the following empty import statement:

      import {} from '@types/googlemaps';
    

    However, now Visual Studio Code complained because the imported type declaration contained no modules, only a namespace:

    [ts] File 'd:/Users/Damir/Git/_BlogSamples/20180824-ionic-namespace-imports/node_modules/@types/googlemaps/index.d.ts' is not a module.

    [ts] Cannot import type declaration files. Consider importing 'googlemaps' instead of '@types/googlemaps'.

  • To stop the editor from complaining, I had to use a triple-slash directive instead:

      /// <reference path="../../../node_modules/@types/googlemaps/index.d.ts" />
    

    I don't like this syntax because the relative path in the directive points into the node_modules folder. Fortunately, there's an alternative syntax available which takes advantage of the standard package resolution:

      /// <reference types="googlemaps" />
    

This last approach doesn't seem too bad, but I dislike it because of a common disadvantage of all three solutions presented so far: as soon as you add the directive to one of the source files, the build will succeed even if you use the types in other source files. If you for some reason remove that file or just the directive from at a later time, it will cause the build to fail for no apparent reason.

That's why I ended up using a completely different solution. I added the following setting to the compilerOptions in the tsconfig.json file:

"typeRoots": [
  "node_modules/@types"
]

This instructs the compiler to automatically include all the type definitions from the node_modules/@types folder where all the external type definitions for the JavaScript libraries without them will be placed. I'm not sure why the setting is not already included when you create a new Ionic project.

When I thought I was already done, I stumbled across one last problem. The unit tests based on the Clicker seed project still failed to build. At first, I couldn't determine the cause:

  • The typeRoots setting was present in the tsconfig.ng-cli.json file.
  • The build was using the tsconfig.spec.json file which extended the above file.

The culprit turned out to be the types setting which was present in the tsconfig.spec.json file. When present, this setting overrides the typeRoots setting. As soon as I deleted types from tsconfig.spec.json, the unit types built successfully as well.

Copyright
Creative Commons License