Как стать автором
Обновить

История про ExecJS или как выполнять джаваскрипт в руби

Время на прочтение4 мин
Количество просмотров3.7K
Допустим, вы пишите крутой вебдванольный проект на рельсах. У вас есть друг Петя — сильный программист на джаваскрипте. Поскольку Петя пишет много и задорно, он решил облегчить себе жизнь и придумал новый язык с красивым и простым синтаксисом, который будет транслироваться в джаваскрипт.
Еще Петя — большой любитель зеленого чая, поэтому назвал он свой новый язык GreenTeaScript.

Кроме Пети, у вас есть еще один друг-программист-на-джаваскрипте Вася. Вася как-то раз придумал написать программу, которая разбирала бы JS на AST, оптимизовала его, а потом собирала обратно в красивый, структурированный код, отсекая всякое лишнее и форматируя по Кроуфорду. Назвал он свое детище BeautifyJS. Кстати, BeautifyJS еще умел собирать AST в минимизированный сжатый код и делал это быстрее и лучше остальных существующих в природе альтернатив.

Поскольку и Петя и Вася ни на чем, кроме джаваскрипта, программировать не умели, свои продукты они на нем и написали.

И тут вам, опять же допустим, первому в мире в голову пришла идея прикрутить эти замечательные штуки к своему крутому вебдванольному проекту на рельсах. Писать фронтенд на GreenTeaScript вам сильно понравилось, а сжатие скриптов с помощью BeautifyJS наверняка сильно ускорило бы сайт.

Теперь начинается самое интересное.

JavaScriptCore


Обе программы написанны на джаваскрипте, а нам нужно как-то их выполнять из рельс. Быстрый гуглинг подсказывает вам, что в макоси, которой вы, допустим, пользуетесь, есть встроенный в систему JavaScriptCore, который можно запустить из консоли командой /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc. Вы быстренько пишете класс, который берет кусок джаваскрипта, скармливает этой штуке, парсит результат и вуаля — все работает! Ништяк.

JScript


Показываете прототип проекта инвестору со своего ноутбука, инвестору нравится, но он говорит, что запуститься надо через неделю. Блин, за неделю вы никак не успеете дописать весь фукнционал, поэтому решаете нанять себе в помощники программиста Диму.
А программист Дима использует операционную систему Windows, поэтому у него макосевый JavaScriptCore не работает. Дима быстренько гуглит и находит решение — JScript, после чего пишет рядом второй класс для работы под винду. И все снова ништяк и работает. На этот раз и под Макось и под Виндоус.

NodeJS


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

therubyracer


Проект стремительно набирает популярность, под дверью скапливается очередь из инвесторов, желающих срочно отвалить вам кучи денег, а сервер перестает справляться. Вы нанимаете сисадмина, в задачи которого будет входить установка и настройка новых серверов. Сисадмин смело берется за дело, накатывает 5 новых серверов, деплоит на них и все снова ломается. Оказывается, на новых серверах версия NodeJS слегка отличается от той, что на первом сервере. А еще лежит она не в /bin/node, а в /usr/local/bin/node. Да и вообще, по мнению админа, внешние зависимости — это не круто и надо, мол, от них избавляться.
Вы вооружаетесь учебником «С++ с нуля за 3 часа» и смело садитесь писать новый гем, который позволял бы использовать движок NodeJS V8 напрямую из руби. В итоге у вас получается therubyracer. При установке гем собирает у себя в песочнице свой собственный V8 нужной версии и напрямую его использует. Лепота!
Теперь можно вычистить ту гору костылей, которая отвечала за выполнение джаваскрипта и заменить все это красивым классом, который просто использует therubyracer. В Gemfile.lock везде прописанна одна версия, все работает быстро и одинаково во всех операционных системах, все счастливы.

therubyrhino


Как-то раз вы вычитываете статью на хабре про JRuby. В статье, среди прочего, написанно что мол, JRuby очень быстрый и вообще крутой. В качестве эксперимента вы решаете запустить свой проект на JRuby и погонять бенчмарки. Естественно, все затыкается на therubyracer, который под JRuby не работает.
Так что вы собираете на коленке новый гем, therubyrhino, который в общем-то делает все то же самое, что и therubyracer, но использует на мозилловский джаваскриптовый движок и работает в JRuby. Погоняли бенчмарки и успокоились, вобщем.

Все вместе


Проект растет ввысь и вширь, количество серверов увеличивается, вы нанимаете по 5 новых программеров в неделю. Каждому новому программеру вы покупаете компьютер, а потом полдня рассказываете, как установить туда компилятор со всеми причиндалами, чтобы therubyracer собрался. А потом он еще собирается минут 10. Вы с грустью вспоминаете дни, когда все работало на встроенном JavaScriptCore без всяких компиляторов и сразу.

К чему весь этот длинный рассказ? А к тому, что в новом Rails 3.1 по умолчанию добавлена поддержка CoffeeScript и UglifyJS. Это почти то же самое, что наши воображаемые GreenTeaScript и BeautifyJS, только всамделишные. И Rails Core Team столкнулось с теми же проблемами, что и мы, только по-настоящему.

Как они их разрулили? Они написали ExecJS — гениальный гем, который позволит использовать все возможные способы выполнения джаваскрипта из руби и при этом не добавляет никаких зависимостей. Он проверяет каждый из способов на доступность, а затем предлагает использовать лучший из найденных.
Таким образом, у разработчика на макоси все будет выполняться через JavaScriptCore, в его соседа на винде через JScript, а под линуксом оно попытается зацепиться за NodeJS или вежливо предложит поставить therubyracer.

Работает все примерно так:

require 'rubygems' # если вы все еще на 1.8
require 'execjs'

# очень полезная функция на джаваскрипте
jsfunc = <<JS
function square(n){
  return n*n;
}
JS

# создаем контекст
context = ExecJS.compile(jsfunc)

# вызываем функцию из контекста
context.call('square', 10) # => 100

# а можно и напрямую
ExecJS.eval 'Math.pow(10, 2)' # => 100


Как видите, все достаточно прозрачно и просто. Не обязательно вникать в детали, оно просто работает самым оптимальным из доступных способов.

Таким образом получается, что гонять джаваскрипт в руби теперь проще пареной репы. По-моему, это очень круто!
Теги:
Хабы:
+58
Комментарии48

Публикации

Изменить настройки темы

Истории

Работа

Ruby on Rails
10 вакансий
Программист Ruby
8 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн