Search code examples
typescriptmodulenode-modulestypescript-typingstsd

How to mix ambient and non-ambient typescript definition files


I have an older typescript project which uses the /typings/tsd.json method of handling .d.ts files, and which also uses Typescript's module keyword to compile source into javascript's IIFE module pattern. The module setting in tsconfig.json (commonjs/amd/etc.) is ignored by that keyword.

For a demo, I am adding to this project some newer typescript code which uses the more typical method of import and export keywords with a SystemJS module loader, with .d.ts files in /node_modules/@types/.

After some Gulp/SystemJS gymnastics, it all works together at runtime and I'm on track to hit my deadline. But I am having trouble at compile-time in one scenario which I'd like to solve.

When I modify the older code to use a class (a model) from the newer code, I'd like the old code to know about the .d.ts of the new code. So I added to the top of the old code's file /// <reference path="../../newercode/feature4/models/NewerModels.d.ts"/>. (Alternately, I put the same line in tsd.d.ts but I get the same result.)

The compiler complains my old code "has or is using private name 'NewModel'".

Inside NewerModels.d.ts the import and export keywords are still there, while none of the .d.ts files already in /typings/ use those keywords. Those keywords are the culprit for the spurious compiler error.

The legacy project wants ambient .d.ts files, newer code produces non-ambient.

Is there anything I can do about it?


Solution

  • My current solution isn't ideal, but it works for demo purposes.

    I create a new folder under /typings/, called /feature4/, and copy the relevant .d.ts files into there. Edit them to remove export keywords, modify as needed to remove import keywords, and replace the class keyword with interface to prevent the patched legacy code from attempting a raw new NewerModel() call.

    That last bit obviously wouldn't work because the newer classes don't actually exist on the global namespace, and the change from class to interface helps discourage this. Since I'm mainly grabbing models, an interface loses no functionality other than requiring new NewerModel() be written as <NewerModel>{}, but I still get compile-time checking on the properties of the model, which is 95% of what I wanted anyway.

    I also have a few services that are treated the same way, but calling methods on them still work due to Dependency Injection. The services were never called from the global namespace to begin with.

    This solution will obviously have a problem if someone changes /newercode/feature4/models/NewerModels.ts, but 1) there shouldn't be any between now and the deadline, and 2) if it's really an issue I could create a Gulp task to automate the copying and editing of the .d.ts files on each build.