Современные приложения

Применение TypeScript в современных приложениях

Рэчел Аппель

Рэчел АппельИзначальной целью создания JavaScript были манипуляции над объектной моделью документов (Document Object Model, DOM) в небольшом дереве DOM. Однако со временем JavaScript стал настолько популярен, что теперь это мейнстримовый язык для написания любых видов приложений — от крошечных программ, предлагаемых в онлайновых магазинах, до корпоративных приложений. Поскольку популярность JavaScript продолжает расти, появление ряда инструментов и языков, необходимых для поддержки разработчиков на JavaScript была неизбежной, и TypeScript становится одним из таких языков.

Что такое TypeScript и как он работает?

TypeScript — это надмножество JavaScript, позволяющее писать и генерировать более строго типизированный и объектно-ориентированный JavaScript-код, но при этом сохраняющее гибкость, которую разработчики так любят в JavaScript (но иногда и ненавидят). TypeScript резко расширяет область применения JavaScript, в том числе его можно использовать в корпоративных приложениях, веб-сайтах и приложениях, где JavaScript исторически занимал лидирующие позиции из-за отсутствия другого инструментария.

Tsc.exe, компилятор и генератор кода TypeScript с открытым исходным кодом, доступен для скачивания с сайта typescriptlang.org. TypeScript — это автономный компилятор, поэтому вы можете в любой момент открыть окно командной строки и запустить tsc.exe с подходящими аргументами, например:

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

Вы пишете TypeScript-код, затем пропускаете его через компилятор и получаете производственный JavaScript-код. Хотя TypeScript — это генератор кода, он не выдает лишний код (как это часто бывает со средствами визуального проектирования), не искажает имена переменных и не меняет порядок переменных. А значит, конечный продукт легче в отладке, так как это чистый JavaScript-код.

JavaScript уже является объектно-ориентированным языком, но его прототипный синтаксис (prototypal syntax) обескураживает многих разработчиков. Для решения этой проблемы TypeScript добавляет в JavaScript такие средства, как классы и интерфейсы; эти средства являются предложенными для стандарта ECMAScript 6 (ES6). Это делает TypeScript генератором кода, обернутым сладкой синтаксической оболочкой, и в большинстве случаев она уменьшает объем JavaScript-кода, который вам приходится поддерживать. Например, следующий код использует прототипный синтаксис:

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();

Предыдущий пример начинается с функции-конструктора — интенсивно используемого JavaScript-шаблона без окружающего определения класса в отличие от других объектно-ориентированных языков. Вы определяете нечто похожее на члены экземпляров классов внутри функций-конструкторов, используя ключевое слово this. Вне функции-конструктора находится реальный метод прототипа, который связывает JavaScript-методы с классами. Классы в TypeScript позволяют писать тот же код, что и в предыдущем примере, но с более естественным синтаксисом, как показано на рис. 1.

Рис. 1. TypeScript-класс

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");
  }
}

Для многих разработчиков пример TypeScript-кода на рис. 1 является более читаемым, чем эквивалент на традиционном JavaScript. Очевидно, что данный код служит определением класса и списка членов, а также показывает типы аргументов. Кроме того, TypeScript обеспечивает проверку типов и поддерживает интерфейсы, проверки статических типов на этапе компиляции (static compile-time checking), выражения в стиле лямбд и прочие радости, обычно присутствующие в компилируемых (а не интерпретируемых) языках. Эти расширения языка JavaScript весьма важны, так как избавляют вас от попадания в распространенные ловушки при кодировании.

Другие распространенные в JavaScript трудности возникают, когда в глобальном пространстве имен JavaScript появляется слишком много открытых переменных, что приводит к «загрязнению» глобального пространства имен (а это случается даже слишком часто). К счастью, TypeScript помогает в этом случае, так как реализует модули, которые ведут себя подобно пространствам имен и создают замыкания (closures), предотвращающие глобальную мешанину кода. Модули в TypeScript бывают двух видов: внутренние и внешние. Внутренние модули содержат код, объявленный в текущем файле, а внешние модули нужно импортировать добавлением ///<reference path=‘path/reference-file.ts’ /> в начало текущего файла кода. Вы можете объявлять модули с помощью ключевого слова module с фигурными скобками. TypeScript-модуль выглядит примерно так:

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

Сгенерированный на его основе JavaScript-код выглядит так:

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

Предыдущий код создает singleton-экземпляр модуля, доступный из любой точки приложения Windows Store в пространстве имен outerModule. Как видите, модули преобразуются в анонимные функции (т. е. Immediately Invoked Function Expressions, IIFE). Любые члены, помеченные директивой export, имеют глобальную область видимости, что эквивалентно C#-членам, помеченным ключевым словом internal (т. е. глобальным на уровне проекта).

Конфигурирование и создание приложений Windows Store с помощью TypeScript

TypeScript и Visual Studio тесно интегрированы, но TypeScript поставляется отдельно, поэтому вам нужно установить следующие средства в дополнение к Visual Studio 2012 редакций Express, Pro или Ultimate:

После установки расширений вы найдете шаблон проекта TypeScript для Visual Studio в узле JavaScript диалогового окна New Project. Этот встроенный шаблон является HTML-шаблоном клиентского веб-приложения с соответствующими ресурсами TypeScript, поэтому для работы этого шаблона больше ничего делать не требуется.

Даже при тесной интеграции Visual Studio и TypeScript на момент написания этой статьи еще не было встроенных TypeScript-шаблонов для проектов Windows Store (только ранее упомянутый шаблон клиентских веб-приложений); однако, как утверждается в документации TypeScript, такие шаблоны вскоре появятся. Тем временем, если вы создаете приложения Windows Store на JavaScript, вы можете использовать любой из существующих шаблонов проектов JavaScript, например Blank, Grid, Split и др. TypeScript автоматически работает в них всех, но от вас потребуется внесение небольших изменений в проект.

Чтобы интегрировать TypeScript в существующее приложение Windows Store, нужно скопировать следующие файлы объявлений в какую-нибудь папку, скажем, <project-root>\tslib:

  • lib.d.ts;
  • winjs.d.ts;
  • winrt.d.ts.

Эти файлы доступны на странице скачивания TypeScript: typescript.codeplex.com. Заметьте, что расширения перечисленных файлов заканчиваются .d.ts, где «d» — это аббревиатура «declaration» (объявление). Данные файлы содержат объявления типов для популярных инфраструктур вроде jQuery или библиотек Windows Runtime (WinRT) и Windows Library for JavaScript (WinJS). На рис. 2 дан пример файла winjs.d.ts, иллюстрирующий часто применяемые WinJS-методы. Также обратите внимание на то, что в этом файле много открытых объявлений, используемых Visual Studio или другими средствами для проверок при компиляции. Поскольку на данный момент TypeScript еще не достиг зрелости, какие-то нужные вам объявления могут отсутствовать, но вы можете добавить их самостоятельно.

Рис. 2. Содержимое файла 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;
    }
// Другие определения

Если вы пишете приложения WinJS, то должны быть хорошо знакомы с этими методами-заглушками (method stubs), в том числе с WinJS.Binding.List и WinJS.xhr; все заглушки из библиотеки WinRT/WinJS — в вашем распоряжении. Эти файлы определений обеспечивают поддержку IntelliSense в Visual Studio.

Добавление файла .ts в любую папку проекта заставляет Visual Studio автоматически создать соответствующие файлы .js и .min.js (minified). TypeScript перекомпилирует эти файлы всякий раз, когда вы сохраняете файл .ts в Visual Studio.

В большинстве шаблонов Windows Store JavaScript папка с именем pages содержит подпапки с собирательными ресурсами .html, .css и .js, необходимыми каждой странице. Помимо этих файлов, в папке \js находятся дополнительные JavaScript-файлы, такие как data.js, default.js и navigator.js. Вы должны интегрировать TypeScript в эти файлы, выполнив для каждого из них следующие операции.

  1. Добавьте ссылки на объявления в начало каждого файла, например ///<reference path=‘path/reference-file.ts’ />.
  2. Переименуйте расширения .js в .ts.
  3. Модифицируйте существующий JavaScript в соответствующие языковые конструкции TypeScript, т. е. модули, классы, объявления и т. д.

Например, чтобы интегрировать \js\data.js, нужно вставить ссылки в начало файла и преобразовать функцию верхнего уровня в модуль, как показано в коде на рис. 3. Если вы переименуете data.js в data.ts, Visual Studio создаст соответствующий .js и файлы сопоставлений при его сохранении.

Рис. 3. TypeScript-преобразование из data.js в data.ts

// Изначальный код в  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: заменить эти данные вашими. Их можно добавлять из
  // асинхронных источников, как только они становятся доступны
  generateSampleData().forEach(function (item) {
    list.push(item);
  });
  // ... другой код для доступа к данным
})();
// Модифицированный файл data.ts
///<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: заменить эти данные вашими. Их можно добавлять из
  // асинхронных источников, как только они становятся доступны
  generateSampleData().forEach(function (item) {
    list.push(item);
  });
  // ... другой код для доступа к данным
}

Код на рис. 3 действует как пространство имен (модуль) верхнего уровня, используя имя проекта, TypeScriptApp, что соответствует стандартным соглашениям, привычным пользователям Visual Studio.

Конечно, вы всегда можете оставить текущий JavaScript-код из шаблонов нетронутым, и этот код будет по-прежнему работать должным образом, но он окажется не согласованным по стилю или синтаксису, что затруднит его дальнейшее сопровождение.

Настройки для TypeScript в Visual Studio

Для оптимального использования TypeScript, особенно в приложениях Windows Store, вы должны знать о некоторых настройках. Поскольку в этих приложениях нет нужды в минимизированных (minified) файлах .js, вы можете сменить значение параметра Minify generated JavaScript на False, открыв вкладку Web Essentials в диалоговом окне Visual Studio Tools | Options и удалить любые такие файлы, если они есть. Минимизированные файлы повышают производительность только веб-сайтов, а не клиентских приложений, так как они уменьшают общие требования к полосе пропускания через Интернет.

Другое необходимое изменение для использования TypeScript в приложениях Windows Store — задать кодировку как «Re-save JS with UTF-8 BOM» (см. bit.ly/ceKkYq) в диалоге Tools | Options. Выбор UTF-8 BOM (byte order mark — метка порядка байтов) ускоряет запуск приложения за счет следования рекомендациям по управлению жизненным циклом процесса Windows Store (подробнее об этих рекомендациях см. мою статью «The Windows Store App Lifecycle» по ссылке msdn.microsoft.com/magazine/jj660301). Кроме производительности, эта спецификация кодировки необходима для прохождения сертификации в Windows Store и публикации вашего приложения.

Разработчикам на JavaScript известно, что для отладки развернутого кода или исходного кода, полученного от JavaScript-генераторов, необходимы средства вроде карт кода (source maps). Карты кода — это файлы, в которых один код сопоставляется с другим, зачастую между файлами стандартного размера на этапе разработки, минимизированными файлами и файлами с комбинированным производственным кодом, которые не так-то просты в отладке. В параметрах Visual Studio установите Generate Source Map в True, чтобы создать карты кода между TypeScript и JavaScript для поддержки отладки TypeScript. Этот параметр применим к ключу компилятора --sourcemap, который в свою очередь создает карты на этапе компиляции.

Компилятор TypeScript по умолчанию транслирует код в совместимый с ECMAScript 3 (ES3); однако вы можете компилировать в ECMAScript 5 (ES5), если измените в Visual Studio параметр «Compile to ECMAScript 3» в False, что приведет к установке для компилятора tsc флага --target и генерации ES5-кода. Этот ключ следует задавать разработчикам, желающим использовать синтаксис в стиле свойств или какие-то ES5- либо ES6-совместимые средства TypeScript (ES6 сейчас находится в стадии предложения).

Дополнительные преимущества

JavaScript никуда не денется и станет еще популярнее, поэтому те, кто перешел на JavaScript из мира компилируемых языков, теперь получили TypeScript, который поможет им писать и управлять JavaScript-кодом. Разработчики на JavaScript выигрывают от дополнительных проверок типов и сервисов компилятора, к которым они ранее не имели доступа при написании обычных JavaScript-приложений.


Рэчел Аппель (Rachel Appel) — разработчик-идеолог в Microsoft New York City. С ней можно связаться через веб-сайт rachelappel.com или по почте rachel.appel@microsoft.com. Также следите за ее заметками в twitter.com/rachelappel.

Выражаю благодарность за рецензирование статьи эксперту Microsoft Кристоферу Бенниджу (Christopher Bennage).