Forest header image

Symfony Finland
Random things on PHP, Symfony and web development

A practical introduction to TypeScript for PHP developers

Most developers I know should be classified with the trendy word polyglot. Polyglot means a person that speaks multiple languages, where as for developers they're programming languages. Even if you consider yourself as a PHP developer, you're likely more diverse.

Programming languages rise and fall in relevance, so your skillset and routine varies over time - depending on trends as well as the line of work you're in. Let's take a practical look at why you might find TypeScript a worthy language to use in 2017 and beyond.

Adding a new language is always an intimidating task, since there is always some overhead in learning not just the language, but the ecosystem around the language. Another hurdle is that adding a new language often requires that you embrace it in a greenfield project.

A greenfield project might be a good way to try a technology, if the scope is limited and risk in general is low. For many kicking off a new project or undertaking a major rewrite is not an option, but you can still evolve and apply good ideas and fresh concepts in your work. The value of legacy in web development is understated.

This is an area I've found TypeScript to be very useful for and I think many developers can benefit from taking a closer look at it. TypeScript compiles down to JavaScript & can be adopted gradually, chances are you'll have plenty of code you can use it on.

Let's examine five separate points that are an advantage when considering adopting TypeScript.

  1. Low overhead in getting started
  2. Great tooling for your favourite editor
  3. Familiar syntax for async programming
  4. Type Definition files
  5. Stability and adoption

1. Low overhead in getting started

Because TypeScript builds directly on standard JavaScript, there is no syntax shock (assuming you know *some* JavaScript). TypeScript adds some syntax and features on top of standard JavaScript / ECMAscript, but compiles down to regular browser (and Node.js) compatible JavaScript. Also, It's not garbled, but human readable.

As said TypeScript needs to be converted from TypeScript to executable JavaScript, there needs to be a conversion process. This does not signal a massive process that needs to be configured and fine tuned. You can do this, and many do but at a minimum you'll need to install a single executable that does the conversion.

Take for example the TypeScript file (hello.ts) below:

let president: string = 'donald';
console.log('hello ' + president);

This will compile down to regular JavaScript with a single command:

tsc hello.ts

with the output being a JavaScript file (hello.js):

var president = 'donald';
console.log('hello ' + president);

In this simple case only the type definition ": string" was removed and "let" replaced with "var" because of my conservative JavaScript target. You can decide if you want to store just the output code to your version control, or if you would prefer to have a deployment time script done it at that time.

2. Great tooling for your favourite editor

In the above code you saw us define the president to be a variable of type string. Optional typing is the name-defining feature of TypeScript, which is handy but not very useful in our case. For large applications typing and references can be invaluable as it's been said that:

You can write large programs in JavaScript. You just can’t maintain them.
- Anders Hejlsberg

Before we move forward to tooling, it's worth noting that you can apply typing gradually in TypeScript. Types don't need to be defined, and in fact they are automatically set with a language feature called type inference.

In the example below, consider that when we don't define the type of the variable, it is assumed to be of the type it was initially set to be:

TypeScript type inference example

You can specify it to be of type "any" if you wish to avoid error highlighting, but all in all any file that is renamed from .js files to .ts files are valid TypeScript by default. This brings us back to the point, which was tooling.

In addition to being just a compiler, TypeScript also provides APIs and tools for IDEs/editors to inspect code during development time. This is especially useful for large applications, but allows catching common errors in smaller applications. Code completion is also working as shown in the above clip.

Good tools are only good if you use them, luckily the TypeScript APIs are open and are be used by many editors. In the case of PHP, many developers are used to using PHPStorm from JetBrains. Out of the box the IDE offers integration with TypeScript that also manages the compilation.

See how our example project is setup to support TypeScript files (.ts) in PHPStorm, complete with code completion and compiler errors reported in the integrated console:

TypeScript support in PHPStorm

There are a number of compiler options that can be changed to configure how the TypeScript compiler works, but these are done universally using the tsconfig.json file that works regardless of your development environment.

3. Familiar syntax for async programming

Asynchronous programming is the hallmark of JavaScript. The ability to continue execution while I/O operations were performed was essential when creating user interface development in the browser, but also unlocks performance benefits when creating networked applications.

Over the time there have been a number of asynchronous development patterns in JavaScript. The most traditional one is using callback functions, which can lead to miserable syntax in larger applications. This was followed by Promises and Generators. From the pair Promises were more approachable and are now widely used.

The TypeScript compiler enables developing with both Promises and Generators by targeting the older ES5 JavaScript standard. However in the upcoming ES7/ES2017 standard there is a new option: Asynchronous functions (async/await)

They might be familiar to you from Hacklang or C#, but since TypeScript 2.1 the feature is available for ES5 browsers and since the TypeScript language builds on JavaScript it is already standard syntax and has been available for sometime for users of Babel, a pure JavaScript transpiler.

The async/await syntax allows writing asynchronous code that looks a lot like synchronous code, so it's easy to understand with a traditional synchronous programming background. If you consider the following example you see some of the concepts mentioned above for together to create something that's quite reasonable for a PHP developer:

sendTweet('Polar bears are behind global warming!');
setInterval(() => {
    console.log('world goes on...')
},1000);

async function sendTweet(message){
    await callTwitterAPI(message);
    console.log('media frenzy ensues');
}

function callTwitterAPI(message){
    return new Promise<void>(resolve => {
        setTimeout(() => {
            console.log('tweet sent ok');
            resolve();
        }, 5000);
    });
}

In the above example the keywords are bolded so that you see where they are located. Essentially you need to define a function that is asynchronous (with the async keyword) and the within that function you can pause execution within the function to wait for a promise to resolve (or be rejected).

In the example there is a delay of 5 seconds when sending the tweet, so the output is

Reggan:ts janit$ node tweet.js 
world goes on...
world goes on...
world goes on...
world goes on...
tweet sent ok
media frenzy ensues
world goes on...
world goes on...
world goes on...

4. Type Definition files

In the above section we looked at how to create an a function that works asynchronously. That example was artifical, so let's improve upon that by using a simple HTTP library called Axios, which has a promise based API that can be used with async/await.

Working with a library's API can be quite cumbersome because you don't have access to code completion and so forth, but to help with this we can use interfaces in TypeScript. Just like in PHP interfaces define the structure of an API, but not the implementation. Together with the Reference annotation in TypeScript, we can point to what we want to have references to at development time - without adding complexity to our build process.

In our case we will simply download the Axios type definition file and start working (this time in Visual Studio Code) to see how adding the reference enables code completion in your editor to files that are otherwise unconnected. In fact in this case the Axios library is loaded from a remote CDN. Let's see how this works in practise:

TypeScript support in PHPStorm

Note that the target file itself has essentially nothing to do with TypeScript. There is a separate interface definition file. You can quite easily write Type Definition files yourself to add type information to existing JavaScript files without modifying them. This was demonstrated in a previous article on Sharing state between JavaScript and Symfony

In fact there are situations when you can't change the loaded JavaScript. One example is Google Analytics, which has an extensive API that would be nicer to work with proper IDE support. Luckily there is already a wide range of Type Definition files created by the TS community under a project called Definitely Typed.

You've got thousands of prewritten definitions available through the Git repository, which you can simply store in your own VCS. The recommended method is to use the Node.js NPM as tool to load and manage Type Declaration files: Simplified Declaration File (.d.ts) Acquisition

5. Stability and adoption

There's a lot more to the language that was mentioned in this article, but most of the above it is even not exclusive to it. As TypeScript builds on top of the official JavaScript specification (ECMAScript), things like async/await, the module system, classes and more are not exclusive to it - but are future proof JavaScript concepts.

During my years of working with Symfony I've grown used to the stability of it. While the syntax may be verbose some times, there are many ways of doing things, I've yet to experience a total collapse of the dungeon (mind me that I was not in the scene when Symfony2 launched ;).

TypeScript and it's JavaScript base represent a similar plateau of stability to me. While the JavaScript scene is described as a hectic place where churn is great, the language itself has been well maintained. Stagnation up until 2009 or so was an issue, but now there is a new JavaScript standard released every year.

As for TypeScript, it was originally an effort purely from Microsoft, but now the developer and userbases are more diverse. The project has a public roadmap that they've managed to adhere to quite well. While the language has evolved, the adoption has grown - especially as a tool for writing frameworks and libraries such as Angular, Ember and NativeScript.

With this background I'm pretty certain that TypeScript will be around in five years in some form or another. And at worst I would have compiled my code to standard JavaScript and continue from there with other tools. TypeScript is easy to adopt, but just as easy to scrap.


Written by Jani Tarvainen on Sunday February 5, 2017
Permalink - Tags: php, typescript, javascript

« Full Stack Symfony B2B eCommerce suite OroCommerce released - My Symfony translations workflow in 2017 »