Некоторое время назад я, в поисках новых инструментов для реализации очередного «домашнего» проекта, наткнулся на Kraken.js — Open Source проект от PayPal. Kraken.js представляет из себя очередной Node.js-фреймворк, основанный на express. Поискав на Хабре, я не обнаружил, ровным счетом, ничего. Встретил только одно упоминание в виде ссылки на главный сайт здесь.
Чем же он меня привлек, и чем он отличается от известных Derby.js, Meteor.js, Sails.js, и др.?
А понравился он мне прежде всего тем, что не накладывает на разработчика совсем уж жестких ограничений (прежде всего на источники данных, на менеджеры пакетов, ...), и при этом вносит некоторую структурированность в код, предлагая следовать MVC-модели. Не хочу здесь подробно останавливаться на всех его плюшках и особенностях, благо все отлично расписано на сайте проекта, а сразу перейду к «своим баранам».
Итак, задача залить Kraken.js-приложение на сервис Heroku, заставить его там работать, и, на сладкое, прикрутить Sockjs.
Оговорюсь, что все описанные далее действия я выполнял на Ubuntu 13.10, но думаю, что для других ОС сильных отличий быть не должно. Для начала идем на heroku.com, регистрируемся и скачиваем Heroku Toolbelt — консольный клиент для работы с Heroku. Вводим в коммандной строке:
$ heroku login
Теперь мы готовы к разворачиванию приложений на Heroku. Следующим шагом установим Kraken.js, для этого выполняем:
$ sudo npm install -g generator-kraken
Затем преходим в каталог с проектами и набираем комманду $ yo kraken
и в интерактивном режиме отвечаем на вопросы генератора:
,'""`. / _ _ |(@)(@)| Release the Kraken! ) __ ( /,'))((`. (( (( )) )) ` `)(' /' [?] Application name: Kraken-Sockets [?] Description: A test kraken application with socks.js [?] Author: <Ваше имя> [?] Use RequireJS? (Y/n) n
Генератор создал каталог одноименный приложению и базовую структуру Kraken.js-приложения. Рассмотрим структуру подробнее:
/config
Тут лежат конфигурационные файлы приложения в формате json
/controllers
Тут весь роутинг и логика
/lib
Сюда можно положить свои и сторонние библиотеки
/locales
Файлы локализации
/models
Модели
/public
Web-ресурсы, статика
/public/templates
Тут лежат шаблоны
/tests
Функциональные и юнит-тесты
index.js
Точка входа в приложение (загрузчик)
Наберем команду npm start
и по адресу localhost:8000 можем полюбоваться работающим Kraken.js-приложением. Хорошо, теперь пришло время залить его на Heroku. Но для начала нужно привести приложение в соответствие с требованиями. Файл package.json
у нас уже есть, теперь нам нужно создать файл Procfile
с таким содержанием:
web: node index.js
Этот файл является инструкцией для Heroku, как запускать наше приложение. Кроме этих файлов Heroku требует, чтобы приложение слушало порт, указанный в переменной окружения PORT
. Через json-конфиги мы этого сделать не сможем. Специально для этих случаев в Kraken.js предусмотрен механизм программного конфигурирования. Метод отвечающий за программную конфигурацию находится в файле index.js
. Мы должны придать ему следующий вид:
app.configure = function configure(nconf, next) {
// Async method run on startup.
nconf.set('port', Number(process.env.PORT || 5000));
next(null);
};
Ну а теперь создадим git-репозиорий проекта:
$ git init
$ git add .
$ git commit -m "init"
И выполним:
$ heroku create kraken-test-socksjs
Heroku создаст нам приложение, доступное по адресу kraken-test-socksjs.herokuapp.com и сразу подключит нам удаленный репозиторий, push в который устанавливает приложение на сервер.
Пробуем:
$ git push heroku master
Заходим на kraken-test-socksjs.herokuapp.com и убеждаемся, что все работает. Но не спешим радоваться; самый первый коммит (например добавим что-нибудь в шаблон главной страницы) ломает все приложение. Дальше у меня ушло много часов просмотра логов (комманда $ heroku logs
) и гугления. А все оказалось очень просто. Heroku, почему-то, теряет некоторые зависимости второго уровня (зависимости зависимостей приложения — звучит кошмарно, приведу пример: в нашем случае он теряет модуль formidable
, от которого зависит модуль kraken-js
). И еще он не выполняет grunt-таски по подготовке ресурсов (компиляция less, шаблонов, и т.д.).
Чтобы устранить эти проблеммы делаем следующее: в файле package.json
переносим все devDependencies
в dependencies
и добавляем в dependencies
модуль "grunt-cli": "~0.1.13"
. Это позволит нам вызывать grunt вручную при деплое приложения. После всех изменений файл package.json
должен принять такой вид:
{
"name": "kraken-sockets",
"version": "0.1.0",
"description": "",
"author": "Your Name",
"main": "index.js",
"scripts": {
"test": "grunt test",
"start": "node index.js",
"postinstall": "./node_modules/grunt-cli/bin/grunt build --force"
},
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"kraken-js": "~0.7.0",
"express": "~3.4.4",
"adaro": "~0.1.x",
"nconf": "~0.6.8",
"less": "~1.6.1",
"dustjs-linkedin": "~2.0.3",
"dustjs-helpers": "~1.1.1",
"makara": "~0.3.0",
"mocha": "~1.17.0",
"supertest": "~0.8.2",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.13",
"grunt-contrib-less": "~0.9.0",
"grunt-dustjs": "~1.2.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-jshint": "~0.6.4",
"grunt-mocha-cli": "~1.5.0",
"grunt-copy-to": "0.0.10"
},
"devDependencies": {
}
}
Обратите внимание на скрипт "postinstall"
, который как раз готовит ресурсы приложения. С проблеммой ресурсов справились, а что делать с пропавшими модулями? Это видимо баг новой системы кэширования модулей Heroku. Напишу им тикет на днях, а пока я справляюсь с помощью плагина heroku-repo. Просто перед каждым «пушем» релиза я чищу кэш модулей коммандой:
$ heroku repo:purge_cache -a kraken-test-socksjs
Ух, с дружбой Heroku и Kraken.js справились, теперь самое приятное: прикрутим к Кракену Sockjs.
Sockjs распространяется в виде npm-модуля и отлично прикручивается к express-приложению, но для того, чтобы ее прикрутить нужен доступ к серверу (тот, что представлен модулем http, на который вешается express.js). Так вот в Kraken.js он хорошо спрятан от разработчика. Но, покопавшись в исходниках Кракена, я нашел как его выцепить. Приведу сразу готовый код, который должен быть вставлен в index.js на место этого участка:
kraken.create(app).listen(function (err) {
if (err) {
console.error(err.stack);
}
});
Вот он:
var k = kraken.create(app);
k.listen(function (err) {
if (err) {
console.error(err.stack);
} else {
var http = k.app.get('kraken:server');
var sockjs_opts = {sockjs_url: 'http://cdn.sockjs.org/sockjs-0.3.min.js'};
var sockjs_echo = sockjs.createServer(sockjs_opts);
sockjs_echo.on('connection', function(conn) {
hub.sockjs_pool.push(conn);
conn.on('data', function(message) {
hub.sockjs_pool.forEach(function(con) {
con.write(message);
});
});
});
sockjs_echo.installHandlers(http, {prefix:'/echo'});
}
});
На этом я закругляюсь. Весь код проекта можно посмотреть на github. Поиграть с развернутым приложением тут.
Очень надеюсь, что эта статься поможет кому-нибудь сэкономить время при отладке Kraken.js-приложений на Heroku и прикручивании к ним всяких вкусностей.
P.S. Это моя первая статья, поэтому конструктивная критика очень приветствуется.
Автор: rodenis