What do you want to learn?
Skip to main content
Practical TypeScript Migration
by Steve Ognibene
Start CourseBookmarkAdd to Channel
Table of contents
The First Conversion
A Tour of CoinCounter
Welcome to module two in Practical TypeScript Migration. This is where we'll convert our first block of code to TypeScript. Before we dive into migration work though, I want to show you the Coin Counter sample application and talk to you about its makeup. Let's get started. I loaded up the module 2 before code in Visual Studio 2013. Throughout this course when I want to launch the project, I'll use the Ctrl+F5 key combination to launch the website in IIS Express without attaching the Visual Studio debugger. This will allow me to debug using the browser's F12 tools if I want to. Coin Counter is a simple game designed to help children learn to count change. The code that I provided includes artwork and monetary values for US coins, but the game could very easily be adapted to any local currency. I'll type my name and press Enter. The game clock starts ticking down and I've been asked to make a certain amount of change. I'll try to do that. Sixty-six cents, quarter, quarter, dime, nickel, penny. Correct! As you can see there's code to handle button clicks, draw pictures of the coins, display success or failure dialogs, and track your scores versus a high score list. I'll click that. I'm not on here yet, Alice, Bob, and Charlie. We'll go to Game and click Resume. It'll count down for the final four seconds. Great, that's #3. I'll hit Close, High Scores, sure enough, I'm down there. I have to say though that my favorite feature of this game is the unit tests. I'll go ahead and do that. This will launch QUnit, which will go through and run the 26 assertions that we set up. Let's talk about how this game is put together.
Choosing a Conversion Candidate
The First Extract and Conversion
Visual Studio TypeScript Build Properties
Preparing Your Team for TypeScript
Using Our Converted GameClock
TypeScript Migration Toolbox and Workflow
Reimplementing Coins as a Class
Migrating CoinCounterViewModel as a Function
Converting CoinCounterViewModel to a Class
Lambdas and How 'this' Works in TypeScript
Completing the Migration and Internal Modules
TypeScript Internal Modules vs. External Modules
Separating Coin and ViewModel Concerns
Okay, the next item is cleaning up the UI logic that's bled into our coin class. I'm going to open up CoinCounterViewModel, and scroll to that initialize method that adds members to each coin at runtime. A coin really shouldn't care if a UI button is enabled, but I do think that each coin object should know if it can have additional coins of its own type added or removed. Let's just get rid of this whole block of code. I want to add some new methods to coin and I'd like to write some unit tests to help me. Since we don't have a cointests file yet, I'll make a copy of gametests and rename it cointests.ts, then I'll clear out pretty much everything except the default test and rename the QUnit testing module as cointests. I'm going to write a test called Can't remove when zero. Inside the test I'll create a new test coin with some junk data, then I'll ensure the current count of my coin object is set to 0. After that I'll assert that a new property on that object called canTakeAway is strictly equal to false. StrictEqual is just QUnit's way of letting you say that you want to assert a quality using the triple equals. This is a super informal flavor of the red-green-refactor test-driven development practice. We've got a failing unit test here, failing to compile is considered a failure, that's our red. Now let's turn it green by implementing the code necessary for it to at least compile. I'll declare canTakeAway as a KnockoutComputed of Boolean, and then implement it in the constructor. This.canTakeAway, Knockout computed, I'll use a lambda here, and we'll just return if the count is greater than 0. Now that my errors are gone, I'll duplicate the previous tests, except this time I'll ensure that a coin can't be added if the coin is already at its max. (Working) Once again, I'll declare and implement the member. (Working) I'll add cointests to my tests.html file and try to run them. Now I haven't modified the view or view model yet, so of course there's an error, but I'm just interested to see if my new unit tests work. I'll run them and good, they came back green. I can pop them both open too, you can see the details of the assertion. Now, let's update the CoinCounterViewModel. I'll add two methods called addCoinEnabled and removeCoinEnabled with the same logic as we had previously, except the functions will accept a coin object as a parameter. This first one will reference canAddMore and removeCoinEnabled will reference canTakeAway. Then I'll search for removeCoin in the index.html file and update the binding. Each coin's remove button will be bound to $parent, which is the view model .removeCoinEnabled passing in $data, which is a coin. Then I'll bind each add button to $parent.addCoinEnabled passing in the coin. Let's try it out. I can add up to 10 pennies and can't subtract once I reach 0. I can also add up to 4 quarters and I'm similarly prevented from going below 0. If I run the tests, everything's still green. Well that seemingly worked pretty well. We saw how TypeScript helped us get the red in our red-green-refactor loop without actually having to run the code, but it may have also been clear again that TypeScript wasn't able to help us with our binding expressions, so keeping the least amount of logic in them as possible can help greatly.
Extracting a Class from CoinCounterViewModel
TypeScript Build and Test Pipeline
Building TypeScript with Grunt and Node.js
Bundling and Minification with Grunt
Automating Unit Tests, TSD, and grunt-ts Visual Studio Support
In this clip, we're going to automate QUnit with Grunt. From the command-line I'll run npm install grunt-contrib-qunit --save-dev. The grunt-contrib-qunit package includes PhantomJS, which is a headless WebKit browser. That means you can launch and use the browser from the command-line without needing a UI. Let's tell Grunt to run our unit tests using PhantomJS. Back in the Gruntfile, I'll tell Grunt to load the grunt-contrib-qunit npm task, which was just installed. Then I'll add a configure entry for QUnit. In the default target, we'll create an options property and specify the URL to our test page, http://localhost:8000/tests/tests.html. Wait a minute. What's that port 8000? We don't have a web server set up there. We don't? Okay, well let's have Grunt start one up temporarily. Back to the command-line. I'll run npm install grunt-contrib-connect --save-dev. Connect is a web server for Node.js. With the grunt-contrib-connect package, Grunt will launch the connect HTTP server so that we can run our tests, and will terminate connect once Grunt is completed. Back in the Gruntfile, let's add the line to load the grunt-contrib-connect npm task, I'll be you're sensing a pattern here, then I'll need to add a config entry for connect. In the default target, I'll add an options property. I need to specify a port and a base for the web server. Dot is fine for the base meaning the same folder as the Gruntfile. Then I need to place connect and QUnit into the default Grunt task, connect needs to come before QUnit because of course we need to start the web server before we can run our tests. Let's run Grunt. Woops, looks like I goofed up my Gruntfile. Yep, I didn't properly close my curly braces on line 42. Let's try again. (Running) Testing. Hey, it worked. 30 assertions passed. Let's mess up something just to see how it looks. I'll go into gameclocktests and change the test on line 14 to say that GameClock should be running before it's started, then I'll run Grunt again and see what happens. See that little F? Cool, sure enough, upon a test failure, it shows what test was running, the expected message on the assertion, and what the actual and expected results were. Cool stuff. I'll go ahead and fix that test back to being correct. Another Node.js tool that's complimentary to TypeScript is TSD. TSD is the command-line alternative to using NuGet to manage TypeScript definitions from DefinitelyTyped. TSD is helpful if you have team members that aren't using Visual Studio or if you just want to standardize on using Node to manage TypeScript. The first thing I'll do is remove all the DefinitelyTyped packages that were installed using NuGet. First, I'll uninstall the big.js DefinitelyTyped definition, then Bootstrap, this one says that it depends on jQuery. I'll go ahead and uninstall that too. Next is Knockout, and finally QUnit. Now that our definitions are gone, TypeScript says that we have 79 errors in our project. I'll open up the Node.js command prompt again. The first thing I'll do is install TSD globally. The command is npm install -g tsd. You might see some errors if you don't have Python installed, don't worry about that, it's an optional dependency. The next thing I'll do is run tsd init. Similarly to how npm init works, this will create a file called tsd.json in the current folder. The way to search for a definition with TSD is by running the query command, for example, I can type tsd query big.js. When I run that, TSD goes up to DefinitelyTyped on GitHub and then prints out any matching libraries. In this case, there's only one. Be aware that the timestamp it shows is the time of the latest commit to DefinitelyTyped altogether, not the latest timestamp for the big.js definition. To install the library, run the same command, but with some extra parameters, tsd query big.js -a, for action, install -s, for save. TSD downloaded the definition file from GitHub and updated our tsd.json file. I'll add that file to our project. (Working) In the tsd.json file, it now lists the definition that we're using and will commit to the DefinitelyTyped repo that it came from. It also mentions this tsd.d.ts file, I'll add that file to the project too. When you install a definition using TSD, it gets added in this file automatically. If there's this file is referenced in your project or on the command-line, any definitions you obtained using TSD will also be referenced automatically and you don't need to bother adding them to your project. Let me reload the project with our macro. Notice our error count now is down to 67, that's because the big.js errors went away. I'll go back to the command-line and install the remainder of our definitions. JQuery, knockout, bootstrap, and qunit. Now when I reload my project, all the errors go away. When it comes time to update a definition obtained by TSD, you have two choices. If you want to update a specific definition, you have to run the same command as you'd run to perform an install, except add an O for Overwrite. There's no space required between the S and the O. If you want to update all definitions to the latest version, you can run tsd update -so. One final command that's useful is if you don't actually check in the definitions to your source control, you could refresh the definitions to the Git commit specified in the tsd.json file by running tsd reinstall -so. There's one last feature of grunt-ts that I'd like to show you. In addition to the normal Grunt target settings like src and files, the grunt.ts task allows you to fetch the files and TypeScript compilation settings identified in the Visual Studio project. Instead of using the src glob, I can specify the vs property and point it to our csproj file. Now when I run Grunt, it'll compile using the settings specified in CoinCounter's current Visual Studio project configuration. This works even on computers that don't have Visual Studio installed like for folks on a different OS, team members with a different editor, or even if you're running grunt-ts on a build server. Grunt-ts also lets you override or ignore the project settings so you can take what you want and customize the rest. For example, under options we have comments set to true. With this option in the Gruntfile, regardless of how the Visual Studio project was set up, Grunt would instruct the TypeScript compiler to preserve the comments. This functionality is fully documented in the grunt-ts readme, including instructions on how to disable the Visual Studio TypeScript build altogether while keeping the language service active. This can be useful if you decided to standardize on a command-line build for your TypeScript and you want to avoid a double compilation.
Repeatable Builds on a Team
Steve Ognibene is an application developer in New York City specializing in SQL Server, C#, VB.NET and TypeScript. Steve is an active contributor to open source projects and is the author of the...
Released17 Apr 2015