В течение последних нескольких лет мне приходится много работать с языком JavaScript и заниматься построением высокомасштабируемых веб-приложений. И хотя у меня «стокгольмский синдром» (JavaScript – один из моих любимых языков программирования), другие разработчики не разделяют моего восторга по поводу этого языка. JavaScript не нравится разработчикам по многим причинам. Одна из них заключается в том, что с JavaScript трудно поддерживать крупную базу кода. Кроме того, в JavaScript вы не найдете некоторых свойств языка, таких как модули, классы и интерфейсы.
Разработчики могут использовать два основных подхода, чтобы избежать подводных камней JavaScript. Первый заключается в работе с чистым JavaScript с шаблонами JavaScript для имитации поведения модулей, классов и других недостающих свойств языка JavaScript. Я в основном использую именно этот подход, но он может оказаться слишком сложным для начинающих разработчиков JavaScript, поскольку здесь нужно знать JavaScript в совершенстве, чтобы избежать возможных ошибок.
Второй подход связан с применением препроцессоров JavaScript. Препроцессоры JavaScript – инструментарий этапа проектирования, который использует традиционные или известные языки, а затем компилирует JavaScript. Данный подход помогает создавать большой объектно-ориентированный код и помогает его поддерживать. Такие препроцессоры JavaScript, как CoffeeScript, Dart или GWT очень популярны, но они заставляют вас учить новые языки или работать с такими языками, как Java или C#, которые затем компилируются в JavaScript. Что если вам придется работать не с JavaScript или вариацией JavaScript?
Вы, вероятно, спросите себя, почему же надо использовать JavaScript или вариант JavaScript, чтобы сделать компиляцию в JavaScript. Одна из причин заключается в том, что ECMAScript 6, последняя спецификация JavaScript, включает большинство недостающих функций языка JavaScript. Кроме того, раз уж в итоге вы в любом случае получаете код JavaScript, почему бы не написать код JavaScript с самого начала? Вот где нам весьма пригодится TypeScript.
TypeScript приходит на помощь
. TypeScript включает новые ключевые слова JavaScript, которые будут доступны, когда ECMAScript 6, следующая версия спецификаций JavaScript, станет стандартом JavaScript. Это означает, что вы можете использовать языковые конструкции, такие как модули и классы, в TypeScript, а позже, после утверждения стандарта ECMAScript 6, ваш код будет уже регулярным кодом JavaScript.
TypeScript – это кроссплатформенный язык, с которым можно работать на любой операционной системе, так как он может применяться везде, где работает JavaScript. Вы можете использовать язык для генерации кода на стороне сервера, написанного на JavaScript, наряду с кодом на стороне клиента, также написанным на JavaScript. Данная возможность призвана помочь вам в написании приложений от начала до конца при помощи лишь одного языка — TypeScript.
Чтобы установить TypeScript, зайдите на сайт TypeScript. Там вы найдете ссылки для скачивания и онлайн-площадку, где сможете протестировать язык. Также вы можете просмотреть демонстрацию TypeScript в разделе сайта «run it». Этот cайт может очень пригодиться новым разработчикам TypeScript.
Не буду углубляться в детали, говоря о функциях TypeScript в данной статье; более подробную информацию вы найдете в статье Дэна Волина «Build Enterprise-Scale JavaScript Applications with TypeScript» (devproconnections.com/javascript/build-enterprise-scale-javascript-applications-typescript). Я рекомендую обратиться к этой статье перед выполнением описанных здесь действий. У вас должно сложиться четкое представление о TypeScript, прежде чем вы приступите к написанию простых приложений от начала до конца с помощью этого языка.
Создание серверной части с Node.js
Чтобы увидеть, насколько прост в работе TypeScript, когда вы трудитесь над написанием приложений, давайте создадим простую галерею фотографий конференции DevConnections. Прежде всего, вам нужно создать серверную часть. Приложение будет использовать среду исполнения node.js, запуская внутреннее приложение.
Node.js – это платформа для создания веб-серверов с помощью языка JavaScript. Она разработана на основе движка Google V8. V8 – среда для исполнения JavaScript, используемая браузером Chrome. Node.js задействует управляемую событиями модель, помогающую создавать успешную реализацию внутренних операций ввода/вывода. В данной статье подразумевается, что вам кое-что известно о node.js и Node Packaged Modules (npm). Если вы незнакомы с node.js, в первую очередь посетите соответствующий сайт.
Наше приложение будет использовать инфраструктуру Express, которая также является каркасом веб-приложения node.js. Благодаря Express веб-приложение на стороне сервера имеет архитектуру MVC. Express позволяет задействовать такие механизмы просмотра, как EJS и Jade, чтобы создавать HTML-файлы для отправки клиенту. Кроме того, Express включает модуль маршрутизации, чтобы вы могли создавать маршруты приложений и иметь доступ к другим функциям, которые помогут вам ускорить создание сервера node.js. Более подробную информацию об Express можно найти на веб-сайте Express.
Создаем проект. Чтобы создать приложение, нужно установить node.js Tools for Visual Studio (NTVS) (nodejstools.codeplex.com/). Следует учесть, что на момент написания этой статьи NTVS находится на этапе первой альфа-версии. NTVS включает в себя шаблоны для проектов node.js, IntelliSense для кода node.js, инструменты контроля и другие функции, которые помогут вам в разработке проекта на node.js внутри Visual Studio IDE.
После того, как вы установили NTVS, создайте бланк приложения Express и назовите его DevConnectionsPhotos. На экране 1 показано диалоговое окно New Project, где включены все установленные шаблоны проекта NTVS.
Экран 1. Окно проекта |
Когда NTVS спрашивает вас, запустить ли npm для установки недостающих зависимостей проекта, следует выбрать вариант запустить npm и получить все пакеты Express.
Создание представлений. В папке Views нужно заменить файл layout.jade с кодом (листинг 1). Этот код записывается в механизм представления Jade, и он выполнит рендеринг макета HTML главной страницы приложения.
Также вы должны заменить файл index.jade, использующий блок контента, который будет входить в layout.jade во время выполнения. Новый код для файла index.jade будет иметь вид, как в листинге 2.
Файл index.jade включает декларацию элемента DIV с Galleria ID. DIV вам пригодится позже на стороне клиента для демонстрации создаваемой фотогалереи.
Реализация на стороне сервера. Прежде чем вы станете использовать TypeScript, необходимо импортировать среду исполнения TypeScript в проект NTVS. Для этого добавьте следующую строку кода в файл DevConnectionsPhotos.njsproj:
Строка кода импортирует TypeScript в проект и позволяет вам компилировать файлы TypeScript. Замечу, что среда исполнения TypeScript Visual Studio не являлась частью проектов NTVS, когда я писал эту статью.
Теперь, когда окружение готово и вы создали главную веб-страницу, переименуйте файл app.js, существующий в корневом каталоге проекта, в app.ts, изменив постфикс на. ts. Выполнение этого действия обусловливает запуск кода TypeScript вместо кода JavaScript. Поскольку TypeScript – это надмножество JavaScript, вы можете без труда преобразовать файл app.js, который является простым шаблоном Express, в app.ts.
В файле app.ts добавьте зависимость модулей на системный модуль node.js. Этот модуль существует под именем fs. Чтобы его использовать, создайте новую переменную под названием fs под комментарием Module dependencies, как показано в листинге 3.
Используйте функцию под названием getAllFileURIs (листинг 4), которая получает имя папки и функцию обратного вызова. Функция getAllFileURIs использует имя папки, чтобы ее открыть; затем она возвратит все файлы URIs в данной папке.
Вы видите, что я использовал лямбду в коде и указал аргументы, которые получает функция. Это функция TypeScript, она не является частью JavaScript.
Написав функцию getAllFileURIs, добавьте конечную точку под названием getAllImages на вашем сервере. Эта конечная точка использует функцию getAllFileURIs, чтобы выбрать все URI для файлов, которые существуют в папке /public/Content/Photos. В листинге 5 показано, как будет выглядеть добавление конечной точки. Когда поступает запрос к конечной точке getAllImages, массив изображений URI сериализуется в формат JSON и записывается в ответ.
Итак, ваш серверный код готов к запуску. Обязательно установите получившийся файл app.js в качестве начального для приложения node.js. На экране 2 показана фотогалерея DevConnections со стороны сервера. Заметьте, что в галерее еще нет фотографий. Теперь, когда мы поработали с серверной стороной, давайте создадим клиентскую сторону.
Экран 2. Стартовый экран |
Создаем клиентскую сторону с помощью библиотек JavaScript
На стороне клиента вы можете задействовать две библиотеки: jQuery и Galleria. Возможно, вы уже знакомы с jQuery; Galleria – это библиотека JavaScript, которая используется для создания галереи из массива изображений. Вы можете загрузить галерею (galleria.io/download) или использовать свою собственную библиотеку, пока настраиваете код.
Установка папок. В общей папке, созданной с помощью шаблона проекта Express, создайте папку Scripts, где будут храниться все сценарии, используемые приложением. Под папкой Scripts добавьте две новые папки, app и lib. Поместите все файлы TypeScript приложения и созданные файлы JavaScript в папку app. Поместите JavaScript-файлы jQuery и Galleria в папку lib.
Чтобы использовать библиотеки JavaScript, как будто они были созданы с TypeScript, следует импортировать файлы декларации библиотек в проект. Файл декларации – это файл, который оканчивается на постфикс. d.ts; он описывает интерфейсы библиотеки. Файл декларации помогает окружению TypeScript понимать типы, включенные в типизированную библиотеку. Однако не все библиотеки имеют файл декларации. Репозитарий GitHub, известный под названием DefinitelyTyped (github.com/borisyankov/DefinitelyTyped), включает большинство из заявленных библиотек. Вы должны загрузить jquery.d.ts и положить его в lib folder. К сожалению, Galleria не включена в DefinitelyTyped. Теперь вы готовы создавать файлы TypeScript и использовать библиотеки.
Действия на клиентской стороне. Первый шаг в настройках на клиенсткой стороне заключается в создании структур данных для информации изображений Galleria и настроек Galleria. Создайте новый файл TypeScript в папке app, находящейся в папке Scripts. Назовите новый файл datastructures.ts. Оба класса, которые вы создадите, будут частью модуля app.data.structures. Код из листинга 6 реализует структуры данных.
Реализация структуры данных очень проста. Структуры данных включают свойства, которые потом приложение будет задействовать для настройки галереи. После создания структуры данных необходимо настроить взаимодействие с сервером и получить изображения для галереи. Для выполнения этих задач необходимо реализовать класс службы данных. Создайте новый файл TypeScript в папке app, которая находится в Scripts. Назовите новый файл dataservice.ts. Служба данных будет вызывать getAllImages и использовать массив изображений для создания галереи изображений, как показано в листинге 6. Как мы видим, один из первых шагов – импортировать модуль app.data.structures. Затем вы объявляете интерфейс, который предоставляет функцию getImages. Функция getImages возвращает объект JQueryPromise, помогающий отложить исполнение операции getImages до успешного выполнения функции getJSON. Когда getJSON будет выполнена, она создаст образ GalleriaImage для каждого изображения URI, являющийся частью массива, который был возвращен сервером.
Теперь, когда у вас есть структуры данных и служба данных, необходимо настроить объект Galleria. Создайте новый файл TypeScript в папке app, которая находится в папке Scripts. Вызовите новый файл bootstrapper.ts. В файле bootstrapper.ts создайте класс Bootstrapper, который отвечает за запуск Galleria object, как показано в листинге 7.
Особого внимания в исполнении заслуживает вызов declare var Galleria. Поскольку у Galleria нет файла декларации, вам нужно объявить его объект. Это тот случай, когда очень удобным оказывается ключевое слово declare. Вы используете ключевое слово declare, чтобы сообщить среде исполнения TypeScript, что объявленная переменная – динамическая и должна использоваться с любым type.
Последняя часть паззла – файл app.ts. Необходимо создать этот файл в папке app, которая находится в папке Scripts. Не путайте файл app.ts node.js и файл app.ts, который используется для запуска приложения. Код из листинга 8 исполняет app.ts файл на стороне клиента.
Теперь, когда мы собрали все части воедино, перед нами конечный результат – фотогалерея (экран 3). Вы можете поместить изображения в папку Images, расположенную под папкой Content.
Экран 3. Фотогалерея |
Преимущества TypeScript
Итак, в этой статье я показал, как построить простое приложение TypeScript. TypeScript помогает вам перебросить мостик к недостающим функциям JavaScript, которые в конечном итоге будут доступны в ECMAScript 6. TypeScript позволяет вам писать сложный код JavaScript и без проблем обслуживать его. Вы можете использовать TypeScript как во внешнем приложении, так и в приложении с инфраструктурой и окружением для JavaScript. Более подробную информацию о TypeScript можно найти в статье Блэра Гринвуда «TypeScript Moves Toward Final Release with Visual Studio 2013 Update 2 CTP2» (http://devproconnections.com/visual-studio-2013/typescript-nears-final-release-visual-studio-2013-update-2-ctp2).
Листинг 1. Рендеринг макета HTML главной страницы приложения.
doctype html html head title='DevConnections Photo Gallery' link(rel='stylesheet', href='/Content/app.css') link(rel='stylesheet', href='/Content/themes/classic/galleria.classic.css') script(src='/Scripts/lib/jquery-1.9.0.js') script(src='/Scripts/lib/galleria-1.2.9.js') script(src='/Content/themes/classic/galleria.classic.js') script(src='/Scripts/app/datastructures.js') script(src='/Scripts/app/dataservice.js') script(src='/Scripts/app/bootstrapper.js') script(src='/Scripts/app/app.js') body block content
extends layout block content div#container header img#DevConnectionsLogo(src='/Content/Images/DevConnctionsLogo.png', alt='DevConnections Logo') h1='DevConnections Photo Gallery' section.main div#galleria img#light(src=«/Content/Images/Light.png»)
Листинг 3. Создание переменной fs
/** * Module dependencies. */ var express = require('express'); var routes = require('./routes'); var user = require('./routes/user'); var http = require('http'); var path = require('path'); var fs = require('fs');
Листинг 4. Функция getAllFileURIs
var getAllFileURIs = function(folder: string, callback: Function): void { var results = [], relativePath = folder.substr(8); fs.readdir(folder, (err, files) => { if (err) { callback([]); }; files.forEach(function(file) { file = relativePath + '/' + file; results.push(file); }); callback(results); }); };
Листинг 5. Добавление конечной точки getAllImages
app.get('/getAllImages', (req, res) => { getAllFileURIs('./public/Content/Photos', (imageUris) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.write(JSON.stringify(imageUris)); res.end(); }); });
Листинг 6. Реализация класса Data Service
/// /// module app.data { import structures = app.data.structures; export interface IDataService { getImages: () => JQueryPromise; } export class DataService implements IDataService { getImages(): JQueryPromise { var deferred = $.Deferred(); var result: structures.GalleriaImage[] = []; $.getJSON(«/getAllImages», {}, (imageUris) => { $.each(imageUris, (index, item) => { result.push(new structures.GalleriaImage(new structures.GalleriaImageConfigOptions(item, «",»«, "My title» + index, «My description» + index, «"))); }); deferred.resolve(result); }); return deferred.promise(); } } }
Листинг 7. Настройка объекта галереи
/// /// declare var Galleria; module app { import data = app.data; export interface IBootstrapper { run: () => void; } export class Bootstrapper implements IBootstrapper { run() { this.getData().then((images) => { Galleria.configure({ imageCrop: true, transition: 'fade', dataSource: images, autoplay: true }); Galleria.run('#galleria'); }); } getData(): JQueryPromise { var dataService = new data.DataService(); return dataService.getImages(); } } }
Листинг 8. Исполнение файла app.ts на стороне клиента
/// module app { // start the app on load window.addEventListener(»DOMContentLoaded", (evt) => { var bootstrapper = new app.Bootstrapper(); bootstrapper.run(); }, false); }