TypeScript Language Service Gotchas When Using Tooling

by Michael Szul on

No ads, no tracking, and no data collection. Enjoy this article? Buy us a ☕.

I ran into a interesting issue while working with TypeScript a few weeks ago. Although TypeScript has a command line interface (CLI), most people interact with it through some form of tooling. This could be as simple as a tsconfig.json project file, but also could be MSBuild tasks within Visual Studio, or tasks with task runners like Grunt or Gulp.

Metron used to use Grunt up until recently, as I used to minify the resulting JavaScript. Since we were already using Grunt for minification, the build process also kicked off the TypeScript compile. In order to prevent checking output code into GitHub, I used post-install scripts to process the TypeScript files after installation.

When I started doing this, I was noticing issues cropping up in the output when installed on machines that would require me to drop into the command line and recompile. Although my TypeScript was completely up-to-date, I was getting different output depending on how I compiled the source.

This is actually a common occurrence if you compile in Visual Studio. Oftentimes, you might have TypeScript installed globally as an npm package, but the Visual Studio tooling is using a different version that was installed as an SDK. This is usually easily fixed by updating your Visual Studio TypeScript SDK, but the issue I was coming across was occurring outside of Visual Studio.

This is where bad architecture comes into play, as well as being careful when you rely too much on tooling. Despite my Visual Studio SDK being up-to-date, and my global TypeScript npm module being up-to-date, AND my locally installed TypeScript dependency being up-to-date, I was still getting compilation errors on the post-install actions.

As it turns out the grunt-typescript npm module for processing TypeScript Grunt tasks had a hard dependency on TypeScript, and this version was vastly out of date. Furthermore, the module itself hadn't been updated in a long time, and the TypeScript module was sub of the node_modules inside of the grunt-typescript module, so it was buried deep in the application dependencies, and wasn't readily apparent. Once I removed the grunt-typescript task, and simply called tsc from the post-install script, the issue resolved.

app
       |--node_modules
            |--typescript (up-to-date)
       |--grunt-typescript
            |--node_modules
                 |--typescript (outdated, but used by the Grunt task)
      

This brings up a few important considerations. For one, always know what your tooling is doing, otherwise, you could end up with errors that are hard to track down.

The other consideration is that tooling and applications that aren't dependent on a specific TypeScript version should leave the installation to the user, and output an error if no TypeScript is found. By including TypeScript--and a specific one at that--this module needlessly handcuffed the task, but also could inadvertently affect all applications using it. This is different, however, from an actual TypeScript module that may require a specific version of TypeScript to actually compile.