Основи роботи з компілятором
Ми розуміємо, що наші читачі вже втомилися від теоретичних пояснень аспектів мови та хочуть приступити до написання та виконання коду. У цьому розділі ми надамо вступні відомості про практику роботи з компілятором ClojureScript.
Компілятор ClojureScript отримує код, розташований у кількох директоріях та просторах імен, та компілює його у JavaScript. Сучасний JavaScript виконується у різних середовищах і кожне з них має свої особливості.
Цей розділ пояснює, як використовувати ClojureScript без додаткових інструментів. Таким чином, ви зрозумієте, як працює компілятор та як його використовувати, коли такі інструменти, як leiningen + cljsbuild або boot, недоступні.
Оточення виконання
Що таке оточення виконання? Оточення виконання — це програмний рушій, що виконує код JavaScript. Найбільш популярне оточення виконання — браузер (Chrome, Firefox та ін.), проте не менш популярним є Node.js.
Існують інші, що не дуже відрізняються від перших двох — Rhino (JDK 6+), Nashorn (JDK 8), QtQuick (QT), тощо. На даний момент ClojureScript компілюється у JavaScript, що може виконуватись у браузері, Node.js і схожих на нього оточеннях.
Встановлення компілятора
Хоча компілятор ClojureScript може бути скомпільований у JavaScript, ефективніше все ж використовувати його оригінальну імплементацію на Clojure для JVM. Для цього слід встановити JDK 8. Сам по собі ClojureScript працює з JDK 7, але зараз ми будемо використовувати його як самостійний інструмент, а він потребує JDK 8, який можна знайти за наступним посиланням: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Останню версію компілятору можна завантажити за допомогою wget
:
wget https://github.com/clojure/clojurescript/releases/download/r1.9.36/cljs.jar
Компілятор ClojureScript запакований як самостійний файл у форматі jar, тому для компіляції коду на ClojureScript у JavaScript вам потрібні лише два файли: JDK 8 та сам компілятор.
Компіляція для Node.js
Почнемо з практичного прикладу: скомпілюємо код для Node.js (далі - "nodejs"). Для цього слід встановити Node.js.
Node.js можна встановити різними способами, але краще зробити це за допомогою nvm ("Node.js Version Manager"). Ви можете прочитати про те, як встановити та використовувати nvm на сторінці проекту.
Після встановлення nvm встановіть останню версію node.js:
nvm install v6.2.0
nvm alias default v6.2.0
Наявність nodejs у системі можна перевірити за допомогою наступної команди:
$ node --version
v6.2.0
Створення застосунку
Для першого прикладу ми створимо структуру проекту та додамо код прикладу.
Створіть файлову структуру для нашого проекту «hello world»
:
mkdir -p myapp/src/myapp
touch myapp/src/myapp/core.cljs
У вас має бути наступна структура директорій:
myapp
└── src
└── myapp
└── core.cljs
Далі додайте цей код у файл myapp/src/myapp/core.cljs
:
(ns myapp.core
(:require [cljs.nodejs :as nodejs]))
(nodejs/enable-util-print!)
(defn -main
[& args]
(println "Hello world!"))
(set! *main-cli-fn* -main)
ЗАУВАЖЕННЯ: Дуже важливо, щоб ім'я простору імен у файлі співпадало зі шляхом до цього файлу у проекті. Таким чином, ClojureScript структурує увесь код.
Компіляція прикладу
Для того? щоб скомпілювати проект, треба створити скрипт, який запустить компілятор з наступною конфігурацією: шлях до коду проекту та шлях до файлу, в який буде записаний JavaScript. Компілятор має багато інших опцій, про які ми поговоримо пізніше.
Створимо файл myapp/build.clj з наступним кодом:
(require '[cljs.build.api :as b])
(b/build "src"
{:main 'myapp.core
:output-to "main.js"
:output-dir "out"
:target :nodejs
:verbose true})
Коротко розглянемо використані опції компілятора:
- Параметр
:output-to
вказує шлях до файлу, в який буде записаний скомпільований код, у нашому випадку це "main.js". - У полі
:main
записане ім'я простору імен, яке є точкою входу у наш застосунок. - У полі
:target
записане ім'я платформи, для якої буде скомпільований код. У цьому випадку це Node.js. За замовчуванням код компілюється для браузеру.
Для запуску компілятора виконайте наступну команду:
cd myapp
java -cp ../cljs.jar:src clojure.main build.clj
Після закінчення спробуйте запустити скомпільований файл у Node.js:
$ node main.js
Hello world!
Компіляція для браузера
У цьому розділі ми створимо схожий застосунок "hello world"
, але тепер скомпілюємо його для виконання у браузері. Вам будуть потрібні ті самі інструменти та веб-браузер.
Процес роботи та структура проекту залишаються незмінними. Ми внесемо зміни лише в файл точки входу, в код застосунку та скрипт компілятора. Створіть таку ж саму структуру в іншій директорії:
mkdir -p mywebapp/src/mywebapp
touch mywebapp/src/mywebapp/core.cljs
Структура проекту повинна виглядати так:
mywebapp
└── src
└── mywebapp
└── core.cljs
Тепер додайте наступний код у файл mywebapp/src/mywebapp/core.cljs
:
(ns mywebapp.core)
(enable-console-print!)
(println "Hello world!")
На відміну від Node.js, проект для браузера не потребує точки входу у вигляді конкретної функції: тут точкою входу буде сам простір імен.
Компіляція прикладу
Компіляція для браузера потребує іншої конфігурації компілятору, тому додайте цей код у ваш скрипт mywebapp/build.clj:
(require '[cljs.build.api :as b])
(b/build "src"
{:output-to "main.js"
:output-dir "out/"
:source-map true
:main 'mywebapp.core
:verbose true
:optimizations :none})
Розглянемо нову конфігурацію::
- У полі
:output-to
записаний шлях до файлу, в який буде записаний скомпільований код, у нашому прикладі це файл "main.js". - У полі
:main
записане ім'я простору імен, яке є точкою входу у застосунок - Додавши поле
:source-map
, компілятор створить source map для скомпільованого коду (source map описує відношення коду на ClojureScript до скомпільованого JavaScript для більш зручного зневадження коду). - У полі
:output-dir
записаний шлях до директорії? в яку будуть поміщені всі оригінальні файли, що будуть скомпільовані. Це потрібно для правильної роботи source maps для усіх залежностей, а не тільки для вашого коду. - Поле
:optimizations
задає рівень оптимізації компілятора. Це поле може мати різні значення, але більш детально про це ми поговоримо пізніше.
Для запуску компіляції виконайте цю команду:
cd mywebapp;
java -cp ../cljs.jar:src clojure.main build.clj
Це може зайняти певний час, тому не хвилюйтесь; зачекайте трохи. JVM разом з компілятором запускається доволі повільно. У наступній частині ми розповімо, як створити процес компілятора, що наглядає за змінами у коді, і тому працює значно швидше.
Поки ви чекаєте, створіть файл HTML, який буде завантажувати скрипт у браузері. Створіть файл index.html з наступним вмістом; файл повинен бути у головній директорії проекту mywebapp.
<!DOCTYPE html>
<html>
<header>
<meta charset="utf-8" />
<title>Hello World from ClojureScript</title>
</header>
<body>
<script src="main.js"></script>
</body>
</html>
Тепер, коли компіляція закінчилась, ви можете відкрити у браузері HTML сторінку і консоль у інструментах розробника і побачити там повідомлення "Hello world!"
Поступова компіляція
Напевно ви вже помітили, як довго стартує компілятор. Для вирішення цієї проблеми у компілятора є можливість компілювати поступово, наглядаючи за змінами у коді і компілюючи тільки те, що змінилось.
Почнемо зі створення скрипта компілятора у файлі watch.clj:
(require '[cljs.build.api :as b])
(b/watch "src"
{:output-to "main.js"
:output-dir "out/"
:source-map true
:main 'mywebapp.core
:optimizations :none})
Тепер виконайте цей скрипт:
$ java -cp ../cljs.jar:src clojure.main watch.clj
Building ...
Reading analysis cache for jar:file:/home/niwi/cljsbook/playground/cljs.jar!/cljs/core.cljs
Compiling src/mywebapp/core.cljs
Compiling out/cljs/core.cljs
Using cached cljs.core out/cljs/core.cljs
... done. Elapsed 0.754487937 seconds
Watching paths: /home/niwi/cljsbook/playground/mywebapp/src
Відкрийте у редакторі простір імен mywebapp.core
і змініть текст, що виводиться у консоль, на "Hello World, Again!"
. Ви побачите, як скомпілюється файл src/mywebapp/core.cljs
, і якщо перезавантажити сторінку index.html
, ви побачите новий текст у консолі.
Рівні оптимізації
Компілятор ClojureScript оптимізується на різних рівнях. За лаштунками ці рівні компяляції забезпечуються компілятором Google Closure Compiler.
Ось спрощене представлення процесу компяліції:
- Читач ClojureScript читає код і проводить його аналіз. На цьому етапі компілятор може повідомити про синтаксичні помилки у коді.
- Після цього компілятор ClojureScript перетворює код у JavaScript. Для кожного файлу з кодом на ClojureScript буде створено файл з перетвореним JavaScript кодом.
- Далі згенеровані файли передаються в Google Closure Compiler, який, в залежності від встановленого рівня оптимізації та іншої конфігурації (sourcemap, output-dir, output-to та ін.), згенерує файл(и) з оптимізованим JavaScript.
Формат коду на виході залежить від обраного рівня оптимізації:
none
На цьому рівні для кожного простору імен буде створено окремий файл з JavaScript без жодних оптимізацій коду.
whitespace
Усі файли будуть зібрані в один, враховуючи порядок залежностей. Переходи на новий рядок та пробіли будуть видалені.
На цьому рівні процес компіляції трохи повільніший, але достатній для невеликих проектів.
simple
Простий рівень оптимізації simple
робить те саме, що whitespace
, а також проводить ряд оптимізацій у виразах та функціях, наприклад, скорочує імена локальних змінних та параметрів функцій.
Рівень :simple
ніколи не змінює функціональність коду, тому, якщо у вас є взаємодія між ClojureScript та зовнішнім оточенням у JavaScript, після оптимізації це також буде працювати.
advanced
На рівні advanced
компілятор робить те саме, що і на рівні simple
, але також виконує ряд агресивних оптимізацій та усуває «мертвий» код. В результаті, розмір коду на виході значно зменшується.
Рівень оптимізацій :advanced
працює лише для строгої підмножини JavaScript, що слідує правилам написання коду, встановленими компілятором Google Closure Compiler. КомпіляторClojureScript генерує код JavaScript, що відповідає цим вимогам, але якщо ви взаємодієте із зовнішнім оточенням JavaScript із ClojureScript, це потребує додаткової конфігурації та можливо змін у коді для того, щоб скомпільований код працював правильно.
У наступних частинах ми розповімо більш детально про цю конфігурацію.