TypeScript first gained support for
tsconfig.json back with the 1.5 release. However, to my lasting regret and surprise Visual Studio will not be gaining meaningful support for it until TypeScript 1.8 ships. However, if you want it now, it's already available to use in beta.
I've already leapt aboard. Whilst there's a number of bugs in the beta it's still totally usable. So use it.
External TypeScript Compilation and the VS build
tsconfig.json is useful and super cool it has limitations. It allows you to deactivate compilation upon file saving using
compileOnSave. What it doesn't allow is deactivation of the TypeScript compilation that happens as part of a Visual Studio build. That may not matter for the vanilla workflow of just dropping TypeScript files in a Visual Studio web project and having VS invoke the TypeScript compilation. However it comes to matter when your workflow deviates from the norm, as mine does. Using external compilation of TypeScript within Visual Studio is a little tricky. My own use case is somewhat atypical but perhaps not uncommon.
I'm working on a project which has been built using TypeScript since TS 0.9. Not surprisingly, this started off using the default Visual Studio / TypeScript workflow. Active development on the project ceased around 9 months ago. Now it's starting up again. It's a reasonable sized web app and the existing functionality is, in the main, fine. But the users want to add some new screens.
Like any developer, I want to build with the latest and greatest. In my case, this means I want to write modular ES6 using TypeScript. With this approach my code can be leaner and I have less script ordering drama in my life. (Yay import statements!) This can be done by bringing together webpack, TypeScript (ts-loader) and Babel (babel-loader). There's an example of this approach here. Given the size of the existing codebase I'd rather leave the legacy TypeScript as is and isolate my new approach to the screens I'm going to build. Obviously I'd like to have a common build process for all the codebase at some point but I've got a deadline to meet and so a half-old / half-new approach is called for (at least for the time being).
Goodbye TypeScript Compilation in VS
Writing modular ES6 TypeScript which is fully transpiled to old-school JS is not possible using the Visual Studio tooling at present. For what it's worth I think that SystemJS compilation may make this more possible in the future but I don't really know enough about it to be sure. That's why I'm bringing webpack / Babel into the mix right now. I don't want Visual Studio to do anything for the ES6 code; I don't want it to compile. I want to deactivate my TypeScript compilation for the ES6 code. I can't do this from the
tsconfig.json so I'm in a bit of a hole. What to do?
Well, as of (I think) TypeScript 1.7 it's possible to deactivate TypeScript compilation in Visual Studio. To quote:
there is an easier way to disable TypeScriptCompile:
.csproj, e.g. in the first
But wait, this means that the legacy TypeScript isn't being compiled any longer. Bear in mind, I'm totally happy with the existing / legacy TypeScript compilation. Nooooooooooooooo!!!!!!!!!!!!!!!
Hello TypeScript Compilation outside VS
Have no fear, I gotcha. What we're going to do is ensure that Visual Studio triggers 2 external TypeScript builds as part of its own build process:
- The modular ES6 TypeScript (new)
- The legacy TypeScript (old)
How do we do this? Through the magic of build targets. We need to add this to our
.csproj: (I add it near the end; I'm not sure if location matters though)
<Exec Command="npm install" />
<Exec Command="npm run build-legacy-typescript" />
<Exec Command="npm run build -- --mode $(ConfigurationName)" />
<Exec Command="npm run clean" />
<_CustomFiles Include="dist\**\*" />
<Message Text="CollectGulpOutput list: %(_CustomFiles.Identity)" />
<_CustomFiles Include="Scripts\**\*.js" />
<Message Text="CollectLegacyTypeScriptOutput list: %(_CustomFiles.Identity)" />
There's a few things going on here; let's take them one by one.
This target triggers our external builds. One by one it runs the following commands:
- Installs the npm packages.
npm run build-legacy-typescript
- Runs the
npm run build -- --mode $(ConfigurationName)
- Runs the
package.jsonand passes through a
modeparameter of either
"Release"from MSBuild - indicating whether we're creating a debug or a release build.
As you've no doubt gathered, I'm following the convention of using the
scripts element of my
package.json as repository for the various build tasks I might have for a web project. It looks like this:
"test": "karma start --reporters mocha,junit --single-run --browsers PhantomJS",
"build-legacy-typescript": "tsc -v&&tsc --project Scripts",
"clean": "gulp delete-dist-contents",
"watch": "gulp watch",
"build": "gulp build"
As you can see,
tsc (which is registered as a
devDependency) to print out the version of the compiler. Then it invokes
tsc again using the
project flag directly on the
Scripts directory. This is where the legacy TypeScript and its associated
tsconfig.json resides. Et voilá, the old / existing TypeScript is compiled just as it was previously by VS itself.
"build" invokes a
gulp task called, descriptively,
"build". This task caters for our brand new codebase of modular ES6 TypeScript. When run, this task will invoke webpack, copy static files, build less etc. Quick digression: you can see there's a
"watch" script that does the same thing on a file-watching basis; I use that during development.
The task that runs to clean up artefacts created by
Since we're compiling our TypeScript outside of VS we need to tell MSBuild / MSDeploy about the externally compiled assets in order that they are included in the publish pipeline. Here I'm standing on the shoulders of Steve Cadwallader's excellent post. Thanks Steve!
CollectGulpOutput respectively include all the built files contained in the
"dist" folders when a publish takes place. You don't need this for when you're building on your own machine but if you're looking to publish (either from your machine or from TFS) then you will need exactly this. Believe me that last sentence was typed with a memory of great pain and frustration.
So in the end, as far as TypeScript is concerned, I'm using Visual Studio solely as an editor. It's the hooks in the
.csproj that ensure that compilation happens. It seems a little quirky that we still need to have the original TypeScript targets in the
.csproj file as well; but it works. That's all that matters.