Moderne Apps

Verwenden von TypeScript in modernen Apps

Rachel Appel

Rachel AppelUrsprünglich war JavaScript für die Dokumentobjektmodell(DOM)-Manipulation in einer kleinen DOM-Struktur gedacht. Mit der Zeit wurde JavaScript allerdings so beliebt, dass es heutzutage eine gängige Sprache für jede Art von App ist, von kleinen Apps für Marktplätze bis hin zu Apps für Unternehmen. Mit der wachsenden Popularität von JavaScript ist es unvermeidlich, dass auch die Anzahl der erforderlichen Tools und Sprachen zur Unterstützung der JavaScript-Entwickler wächst, und eine dieser Sprachen ist TypeScript. 

Definition und Funktionsweise von TypeScript

TypeScript ist eine Obermenge von JavaScript, mit der Sie JavaScript-Code schreiben können, der sich stärker typisiert und objektorientierter verhält, dabei aber die Flexibilität bewahrt, die die Entwickler an JavaScript schätzen (oder manchmal hassen). Mit TypeScript kann JavaScript sinnvoll für Unternehmens-Apps, Websites und Apps verwendet werden; Bereiche, in denen JavaScript in der Vergangenheit im Sande verlief, da es keine entsprechenden Tools gab.

Unter typescriptlang.org steht „TSC.exe“, ein Open-Source-Compiler/-Code-Generator für TypeScript zum Download zur Verfügung. TypeScript ist ein eigenständiger Compiler, daher können Sie eine Eingabeaufforderung öffnen und „TSC.exe“ jederzeit mit den geeigneten Argumenten darin ausführen, wie im folgenden Beispiel:

tsc.exe --out outputfile.js inputfile.ts

Sie schreiben TypeScript-Code, lassen ihn durch den Compiler laufen und erhalten Produktions-JavaScript. Obwohl TypeScript ein Code-Generator ist, enthält die Ausgabe keinen unnötigen Code (wie es häufig bei einem visuellen Entwurfstool der Fall ist), es werden keine Variablen zergliedert, und ebenso wenig wird die Reihenfolge der Variablen geändert. Das erleichtert das Debuggen des endgültigen Produkts, da dieses schnörkelloser JavaScript-Code ist.

JavaScript ist bereits eine objektorientierte Sprache, aber seine prototypische Syntax wirkt auf viele Entwickler abschreckend. TypeScript löst dieses Problem, indem es JavaScript Features wie Klassen und Schnittstellen hinzufügt. Dies sind Features, die vom ECMAScript 6(ES6)-Standard vorgeschlagen werden. TypeScript wird dadurch zu einem syntaktisch versüßten Code-Generator, der in den meisten Fällen die Menge des zu verwaltenden JavaScript reduziert. Die prototypische Syntax wird zum Beispiel im folgenden Code verwendet:

function Animal(name, species, habitat) {
  this.name = name;
  this.species = species;
  this.habitat = habitat;
}
Animal.prototype.sayHello = function(){
  console.log("RAWR!");
}
var animal =   new Animal("Fluffy", 
  "Velociraptor ", 
  "Everywhere. Run and hide.");
animal.sayHello();

Der Code im vorigen Beispiel beginnt mit einer Konstruktorfunktion, einem häufig verwendeten JavaScript-Muster ohne die umgebende Klassenfunktion, die Sie normalerweise in anderen objektorientierten Sprachen vorfinden. Sie definieren in Konstruktorfunktionen mit dem this-Schlüsselwort Elemente, die den Instanzmembern von Klassen ähneln. Die eigentliche Prototypmethode, die JavaScript-Methoden an Klassen bindet, befindet sich außerhalb der Konstruktorfunktion. Mit Klassen in TypeScript können Sie denselben Code wie im Beispiel oben schreiben, aber eine natürlichere Syntax verwenden, wie in Abbildung 1 gezeigt.

Abbildung 1: Eine TypeScript-Klasse

class Animal
{  
  name: string;
  species: string;
  habitat: string;
  constructor(name: string, species: string, habitat: string)
  {
    this.name = name;
    this.species = species;
    this.habitat = habitat;
  }
  sayhello()
  {
    Console.log("RAWR");
  }
}

Viele Entwickler finden das TypeScript-Codebeispiel in Abbildung 1 leichter lesbar als die herkömmliche JavaScript-Entsprechung. Der Code dient deutlich als eine Klassendefinition und Memberliste, und er zeigt die Typen der Argumente. TypeScript bietet auch Typenüberprüfung, Schnittstellen, statische Kompilierzeitüberprüfung, Ausdrücke im Lambdastil und Extras, die in der Regel in kompilierten – nicht interpretierten – Sprachen zu finden sind. Diese Erweiterungen der JavaScript-Sprache sind hilfreich, da Sie mit ihnen häufige Fallen bei der Programmierung vermeiden.

Andere verbreitete JavaScript-Probleme entstehen, wenn der globale JavaScript-Namespace zu viele Variablen mit öffentlichem Gültigkeitsbereich umfasst und dadurch eine globale Namespaceüberschwemmung verursacht wird (was scheinbar viel zu häufig vorkommt). Glücklicherweise sorgt TypeScript in diesem Fall für Abhilfe, indem es Module implementiert, die sich wie Namespaces verhalten und Closures erstellen, um die globale Überfrachtung zu vermeiden. In TypeScript gibt es zwei Modulvarianten: intern und extern. Interne Module enthalten Code, der in der aktuellen Datei deklariert wird. Zum Importieren externer Module fügen Sie oben in der aktuellen Codedatei Code nach folgendem Muster hinzu: „///<reference path=‘Pfad/Verweisdatei.ts’ />“ Sie deklarieren Module mit dem module-Schlüsselwort und benötigen zum Schließen nur geschweifte Klammern. Ein TypeScript-Modul sieht etwa folgendermaßen aus:

module outerModule {
  "use strict";
  module innerModule {
    export function aFunction { s: string };
    export var variable = 1;
  }
}

Ausgegeben wird der folgende JavaScript-Code:

var outerModule;
(function (outerModule) {
  "use strict";
  var innerModule;
  (function (innerModule) {
    function aFunction() { s: string }
    innerModule.aFunction = aFunction;
    innerModule.variable = 1;
  })(innerModule || (innerModule = {}));
})(outerModule || (outerModule = {}));

Der vorige Code erstellt eine Singletonmodulinstanz, auf die ein Zugriff von einer beliebigen Stelle in einer Windows Store-App im outerModule-Namespace möglich ist. Wie Sie sehen, werden die Module als anonyme Funktionen dargestellt, d. h. direkt aufgerufene Funktionsausdrücke (Immediately Invoked Function Expression, IIFE). Jedes Member, das mit der export-Direktive markiert ist, hat einen globalen Gültigkeitsbereich. Das entspricht C#-Membern, die mit dem internal-Schlüsselwort gekennzeichnet sind (d. h. projektweit).

Konfigurieren und Erstellen von Windows Store-Apps mit TypeScript

TypeScript und Visual Studio sind eng integriert, aber TypeScript ist getrennt erhältlich. Sie müssen daher zusätzlich zu Visual Studio 2012 Express, Pro oder Ultimate folgende Tools installieren:

Nach dem Installieren der Erweiterungen finden Sie eine TypeScript-Projektvorlage für Visual Studio im Dialogfeld „Neues Projekt“ direkt unter dem JavaScript-Knoten. Diese spezielle, integrierte HTML-Client-Vorlage für Web-Apps enthält bereits die geeigneten TypeScript-Ressourcen und funktioniert somit ohne Ihr weiteres Zutun.

Trotz der engen Integration von TypeScript in Visual Studio sind zum Zeitpunkt der Verfassung dieses Artikels keine integrierten Windows Store-Projektvorlagen mit TypeScript vorhanden (nur die bereits erwähnte Webvorlage), laut TypeScript-Dokumentation sind diese aber in naher Zukunft vorgesehen. In der Zwischenzeit verwenden Sie zum Erstellen von Windows Store-Apps mit JavaScript eine beliebige der aktuellen JavaScript-Projektvorlagen, darunter zum Beispiel „Leere App“, „Raster-App“ und „Geteilte App“. TypeScript funktioniert automatisch in jeder von ihnen, aber Sie müssen für die Arbeit mit TypeScript einige geringfügige Änderungen am Projekt vornehmen. 

Um TypeScript in eine vorhandene Windows Store-App zu integrieren, kopieren Sie die folgenden Deklarationsdateien in einen Ordner, zum Beispiel „<projektstamm>\tslib“:

  • „lib.d.ts“
  • „winjs.d.ts“
  • „winrt.d.ts“

Diese Dateien stehen auf der TypeScript-Downloadseite unter typescript.codeplex.com zur Verfügung. Die Dateierweiterungen für die aufgelisteten Dateien enden mit „.d.ts“, wobei das „d“ für Deklaration steht. Diese Dateien enthalten Typdeklarationen für beliebte Frameworks wie jQuery oder das systemeigene Windows-Runtime(WinRT)-Framework sowie Windows-Bibliothek für JavaScript(WinJS)-Bibliotheken. Abbildung 2 enthält ein Beispiel für die Datei „winjs.d.ts“ mit häufig verwendeten WinJS-Methoden. Die Datei umfasst zahlreiche öffentliche Deklarationen, die von Visual Studio oder anderen Tools für Überprüfungen zur Kompilierzeit verwendet werden. Einige fehlen hier möglicherweise, da TypeScript zu diesem Zeitpunkt noch nicht ganz ausgereift ist, aber Sie können sie selbst hinzufügen.

Abbildung 2: Die Definitionsdatei „winjs.d.ts“

declare module WinJS {
  export function strictProcessing(): void;
  export module Binding {
    export function as(data: any): any;
    export class List {
      constructor (data: any[]);
      public push(item: any): any;
      public indexOf(item: any): number;
      public splice(index: number, count: number, newelems: any[]): any[];
      public splice(index: number, count: number): any[];
      public splice(index: number): any[];
      public createFiltered(predicate: (x: any) => bool): List;
      public createGrouped(keySelector: (x: any) => any, dataSelector:
         (x: any) => any): List;
        public groups: any;
        public dataSource: any;
        public getAt: any;
      }
      export var optimizeBindingReferences: bool;
  }
  export module Namespace {
    export var define: any;
    export var defineWithParent: any;
  }
  export module Class {
    export function define(constructor: any, instanceMembers: any): any;
    export function derive(
      baseClass: any, constructor: any, instanceMembers: any): any;
    export function mix(constructor: any, mixin: any): any;
  }
  export function xhr(options: { type: string; url: string; user: string;
     password: string; headers: any; data: any;
     responseType: string; }): WinJS.Promise;
  export module Application {
    export interface IOHelper {
      exists(filename: string): bool;
      readText(fileName: string, def: string): WinJS.Promise;
      readText(fileName: string): WinJS.Promise;
      writeText(fileName: string, text: string): WinJS.Promise;
      remove(fileName: string): WinJS.Promise;
    }
// More definitions

Wenn Sie WinJS-Apps schreiben, sind Sie mit diesen Methodenstubs wahrscheinlich recht vertraut, einschließlich der WinJS.Binding.List- und WinJS.xhr-Objekte; die gesamten WinRT-/WinJS-Bibliotheksstubs stehen Ihnen zur Verfügung. Durch diese Definitionsdateien funktioniert IntelliSense in Visual Studio.

Wenn Sie einem beliebigen Ordner im Projekt eine TS-Datei hinzufügen, erstellt Visual Studio automatisch die entsprechenden JS- und MIN.JS-Begleitdateien (letztere ist minimiert). TypeScript erstellt diese Dateien jedes Mal erneut, wenn Sie eine TS-Datei in Visual Studio speichern.

In den meisten der Windows Store-JavaScript-Vorlagen enthält ein Ordner namens „pages“ Unterordner mit den gesammelten HTML-, CSS- und JS-Ressourcen, die für jede Seite erforderlich sind. Zusätzlich zu diesen Dateien enthält der Ordner „\js“ weitere JavaScript-Dateien, darunter „data.js“, „default.js“ und „navigator.js“. Sie müssen TypeScript in diese Dateien integrieren, indem Sie für jede die folgenden Schritte ausführen:

  1. Fügen Sie oben in jeder Datei Deklarationsverweise hinzu, zum Beispiel „///<reference path=‘Pfad/Verweisdatei.ts’ />“.
  2. Benennen Sie die JS-Dateien mit der Dateierweiterung TS um.
  3. Ändern Sie vorhandenen JavaScript-Code in entsprechende TypeScript-Sprachkonstrukte, d. h. Module, Klassen, Deklarationen und so weiter.

Um beispielsweise „\js\data.js“ zu integrieren, müssen Sie Verweise oben in die Datei einfügen und die Funktion der obersten Ebene in ein Modul konvertieren, wie im Code in Abbildung 3. Wenn Sie „data.js“ in „data.ts“ umbenennen, erstellt Visual Studio die entsprechenden JS- und Zuordnungsdateien beim Speichern der Datei.

Abbildung 3: TypeScript-Transformation von „data.js“ zu „data.ts“

// Original code in data.js
(function () {
  "use strict";
  var list = new WinJS.Binding.List();
  var groupedItems = list.createGrouped(
    function groupKeySelector(item) { return item.group.key; },
    function groupDataSelector(item) { return item.group; }
  );
  // TODO: Replace the data with your real data
  // You can add data from asynchronous sources
  // whenever it becomes available
  generateSampleData().forEach(function (item) {
    list.push(item);
  });
  // ... More data-access code
})();
// The modified data.ts file
///<reference path='../ts/winjs.d.ts' />
///<reference path='../ts/winrt.d.ts' />
  module TypeScriptApp {
    "use strict";
    var list = new WinJS.Binding.List();
    var groupedItems = list.createGrouped(
      function groupKeySelector(item) { return item.group.key; },
      function groupDataSelector(item) { return item.group; }
  );
  // TODO: Replace the data with your real data.
  // You can add data from asynchronous sources whenever it becomes available
  generateSampleData().forEach(function (item) {
    list.push(item);
  });
  // ... More data-access code
}

Der Code in Abbildung 3 verhält sich wie ein Namespace der obersten Ebene (Modul), der den Projektnamen TypeScriptApp verwendet und Standardkonventionen beibehält, die Benutzern von Visual Studio vertraut sind.

Sie können den aktuellen JavaScript-Code aus den Vorlagen natürlich auch unverändert lassen. Der Code wird dennoch wie erwartet ausgeführt, aber er ist hinsichtlich des Stils und der Syntax inkonsistent, was die Verwaltung erschwert.

Visual Studio-Optionen für TypeScript

Damit Sie TypeScript optimal verwenden können – insbesondere in Windows Store-Apps – müssen Sie über einige Einstellungen Bescheid wissen. Da in Windows Store-Apps keine minimierten JS-Dateien erforderlich sind, können Sie im Visual Studio-Dialogfeld „Extras | Optionen“ auf der Registerkarte „Web Essentials“ die Option zum Minimieren des generierten JavaScript durch „False“ deaktivieren und alle möglicherweise vorhandenen JS-Dateien löschen. Minimierte Dateien verbessern die Leistung nur in Websites, nicht in Client-Apps, da sie die insgesamt benötigte Bandbreite über das Internet verringern.

Zur Verwendung von TypeScript in Windows Store-Apps müssen Sie außerdem im Dialogfeld „Extras | Optionen“ die Option zum Codieren in „Re-save JS with UTF-8 BOM“ ändern (siehe bit.ly/ceKkYq). Durch die Verwendung der UTF-8-Bytereihenfolge-Marke (Byte Order Mark, BOM) bietet die App eine bessere Leistung beim Start und entspricht den Richtlinien zur Lebenszyklusverwaltung für den Windows Store. (Weitere Informationen zu diesen Richtlinien finden Sie in meinem Artikel „Der Lebenszyklus einer Windows Store-App“ in der Windows 8-Sonderausgabe unter msdn.microsoft.com/magazine/jj660301). Neben der Leistung ist diese Codierungsspezifikation erforderlich, um die Windows Store-Zertifizierung zu erhalten und die App zu veröffentlichen.

JavaScript-Entwickler wissen, dass Tools wie Quellzuordnungen notwendig sind, um bereitgestellten Code oder Quellcode von JavaScript-Generatoren zu debuggen. Der Grund dafür ist, dass Quellzuordnungen Dateien sind, die Code anderem Code zuordnen. Häufig erfolgt diese Zuordnung zwischen Entwicklungsdateien in Standardgröße und minimierten und kombinierten Produktionsdateien, die schwierig zu debuggen sind. Legen Sie für „Generate Source Map“ in den Visual Studio-Optionen „True“ fest, um Quellzuordnungen zwischen TypeScript und JavaScript zu erstellen und das TypeScript-Debuggen zu aktivieren. Diese Option wendet den Compilerschalter „--sourcemap“ an, der wiederum zur Kompilierungszeit die Zuordnungen erstellt.

Der TypeScript-Compiler kompiliert standardmäßig in Code, der dem ECMAScript 3(ES3)-Standard entspricht. Sie können aber in Code kompilieren, der dem ECMAScript 5(ES5)-Standard entspricht, indem Sie für die Option „Compile to ECMAScript 3“ in Visual Studio „False“ festlegen, wodurch das tsc-Compilerkennzeichen auf „--target“ gesetzt wird, um ES5-Code zu generieren. Legen Sie diesen Schalter fest, wenn Sie Syntax im Eigenschaftenstil oder beliebigen ES5-Code oder vorgeschlagene ES6-TypeScript-Features verwenden möchten.

Zusätzliche Vorteile

JavaScript ist dauerhaft etabliert und beliebter als je zuvor. Den Entwicklern, die aus dem Bereich der kompilierten Sprachen kommen und JavaScript verwenden, hilft TypeScript dabei, JavaScript-Code für skalierbare Anwendungen zu schreiben und zu verwalten. JavaScript-Entwickler profitieren von der zusätzlichen Typüberprüfung und Compilerdiensten, auf die sie in der Regel nicht zugreifen können, wenn sie direkten JavaScript-Code schreiben.

Rachel Appel arbeitet als Developer Evangelist bei Microsoft in New York City. Sie kann über ihre Website unter rachelappel.com oder per E-Mail unter rachel.appel@microsoft.com erreicht werden. Sie können auch Rachel Appels Neuigkeiten auf Twitter unter twitter.com/rachelappel verfolgen.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Christopher Bennage (Microsoft).
Christopher Bennage ist Entwickler im Patterns & Practices-Team von Microsoft. Seine Aufgabe ist es, Methoden zu erforschen, zu sammeln und weiterzugeben, mit denen die Softwareentwicklung Spaß macht. In letzter Zeit hat er sich vor allem mit JavaScript und (gelegentlich) mit der Spieleentwicklung beschäftigt. Seinen Blog finden Sie unter dev.bennage.com.