На прошлом JPoint пообещал написать статью про использование GraalVM для смешивания Java и JS. Вот она.


В чем проблема? В повседневной практике часто встречаются приложения, состоящие из двух частей: JavaScript-фронтенд и Java-бэкенд. Организация интеропа между ними требует усилий. Как правило, делают их люди с разных сторон баррикад, и при попытке залезть в чужую область они начинают страдать. Еще есть фуллстек веб-разработчики, но про них всё понятно: они должны страдать всегда.


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


Если кто-то из джавистов еще не писал на React, то здесь будет туториал, позволяющий это сделать. Если кто-то из джаваскриптеров не пробовал писать на Java, то в этом же туториале получится к ней прикоснуться (правда, всего одной строчкой и сквозь JS-биндинги).


JS->Java. Тряхнем стариной: Nashorn


Если хочется интероп Java->JS, такая технология в JDK давным-давно была, и называется она Nashorn (читается: «Насхорн»).


Давайте возьмем какую-нибудь реальную ситуацию. Люди из раза в раз, из года в год, продолжают писать «серверные» валидаторы на Java и «клиентские» валидаторы на JS. Особый цинизм тут в том, что проверки зачастую совпадают на 80%, и вся эта активность, по сути, — особая форма бездарно потерянного времени.


Представим, что у нас есть очень тупой валидатор:


var validate = function(target) { 
    if (target > 0) {
        return "success"; 
    } else {
        return "fail"; 
    }
};

Запустить мы его можем на всех трех платформах:


  • Браузер
  • Node.js
  • Java

В браузере это тривиально. Просто встраиваем этот кусок кода куда угодно, и оно работает.


В Node.js надо либо уважать их феншуй по использованию require, либо хакнуть его к чертям вот таким простым кодом:


var fs = require('fs');
var vm = require('vm');
var includeInThisContext = function(path) {
var code = fs.readFileSync(path);
vm.runInThisContext(code, path); }.bind(this); includeInThisContext(__dirname + "/" + filename);

Готовый пример есть у меня на GitHub.


Готовьтесь к тому, что если вы пользуетесь такими приемами, то довольно скоро коллеги могут начать считать вас чучелом. Нам, джавистам — не привыкать, а вот профессиональные джаваскриптеры могут и оконфузиться.


Теперь долбанем всё то же самое, но под Насхорном в Java.


public class JSExecutor {
    private static final Logger logger = LoggerFactory.getLogger(JSExecutor.class);

    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    Invocable invoker = (Invocable) engine;

    public JSExecutor() {
        try {
            File bootstrapFile = new ClassPathResource("validator.js").getFile();
            String bootstrapString = new String(Files.readAllBytes(bootstrapFile.toPath()));
            engine.eval(bootstrapString);
        } catch (Exception e) {
            logger.error("Can't load bootstrap JS!", e);
        }
    }

    public Object execute(String code) {
        Object result = null;
        try {
            result = engine.eval(code);
        } catch (Exception e) {
            logger.error("Can't run JS!", e);
        }
        return result;
    }

    public Object executeFunction(String name, Object... args) {
        Object result = null;
        try {
            result = invoker.invokeFunction(name, args);
        } catch (Exception e) {
            logger.error("Can't run JS!", e);
        }
        return result;
    }
}

Этот пример тоже есть у меня на GitHub.


Как видите, можно дернуть как произвольный код, так и отдельную функцию по ее имени.


Есть, конечно, такие проблемы, которые можно решить только в ручном порядке. Например, можно состряпать полифилл типа такого:


var global = this;
var window = this; 
var process = {env:{}};
var console = {}; 

console.debug = print; 
console.log = print; 
console.warn = print; 
console.error = print;

Если хочется, чтобы код валидатора был идеально идентичным и на сервере, и на клиенте, придется для «серверных» методов написать заглушки, которые подпихиваются только в браузер. Это уже детали конкретной реализации.


Кстати, ab на моем ноутбуке (ab -k -c 10 -n 100 http://localhost:3000/?id=2) на такой код показывает 6-7 тысяч запросов в секунду, и не важно, на чем он запущен — на Nashorn или Node.js. Но в этом ничего интересного: во-первых, ab на локалхосте измеряет погоду на Марсе, во-вторых, мы и так верим, что явных ляпов в этих движках нет, они конкуренты.


Понятно, что, если вы живете в «красной зоне» кривой имени Ш., использовать Nashorn без включения мозга и написания бенчмарков нельзя. Если хорошенько подумать, можно написать такой бенчмарк, где Насхорн будет проседать, и правильней будет написать нативный код. Но надо четко понимать, что мир не ограничивается хайлоадом и перформансными темами, иногда удобство написания важней любого перформанса.


Java->JS. Проблема


Попробуем пропихнуть данные в обратном направлении, из Java в JS.


Зачем это может быть нужно?


Во-первых, что самое главное, во многих компаниях существует необсуждаемая аксиома: мы используем Java. В каких-нибудь банках. Во-вторых, по ходу решения повседневных проблем такие задачи возникают постоянно.


Рассмотрим игрушечный случай из реальной жизни. Представьте: нужно сгенерить фронт вебпаком, и хочется вписать в правый верхний угол веб-странички текущую версию приложения. Вполне вероятно, что версию бэкенда можно нормальным способом вытащить только вызвав какой-то джавовый код (легаси же). Значит, нужно создать такой Maven-проект, который будет работать в два прохода: прибить к какой-нибудь фазе Maven Build Lifecycle сборку пары классов и их запуск, которые сгенерят properties-файл с номером версии, который на следующей фазе подхватит вручную вызванный npm.


Приводить пример такого pom.xml я здесь не буду, потому что это мерзко :)


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


  • Разработчики хотят использовать тот язык, который более всего подходит к решаемой задаче. Очень больно писать на Java веб-интерфейс (по крайней мере до тех пор, пока JVM и OpenJDK не стабилизируются на WebAssembly), а на JS он делается просто и удобно.
  • Часто хочется параллельно развивать несколько кодовых баз. Например, есть одна база на JS — фронт, и другая база на Java — бэк. Хочется развивать проекты, потихоньку переписывая всё приложение на Node.JS, включая серверный код — в тех местах, где Java не нужна по смыслу. Не должно быть «дня номер ноль», когда весь Java-бэкенд или JS-фронтенд отправляется на свалку, и пусть весь мир подождет, пока мы напишем новый.
  • При пересечении границы языка приходится вручную писать множество мусорного кода, обеспечивающего интероп.

Иногда есть готовые решения — например, переход границы Java/С делается с помощью JNI.


Использование такой интеграции еще и тем хорошо, что, как любят говорить программисты-функционалы, «не сломается то, чего нет». Если мы в своем коде поддерживаем адовейшие pom.xml, properties и xml-файлы и другой ручной интероп, то они имеют свойство ломаться в самых неприятных моментах. Если же эту прослойку написали какие-нибудь реальные боевые ботаны, типа Oracle или Microsoft, оно почти не ломается, а когда ломается — чинить это не нам.


Возвращаясь к предыдущему примеру: зачем нам вставать два раза и делать чудеса с Насхорном, если можно не вставать вообще и писать весь UI только на Ноде?


Но как это сделать, учитывая, что нужно прозрачно посасывать данные из Java?


Первая мысль, которая приходит в голову — продолжать использовать Nashorn. Засосать в него все нужные библиотеки, подпилить напильником, и, может быть, они даже запустятся. Если среди них не будет таких, которым нужны нативные расширения. И вручную сэмулировать всю инфраструктуру Ноды. И еще что-то. Кажется, это проблема. Вообще, такой проект уже был, назывался Project Avatar, и, к сожалению, он загнулся. Если разработчики из Oracle не смогли его довести до конца, то какой шанс, что получится сделать это самостоятельно?


Java->JS. Graal


К счастью, у нас есть еще один довольно новый и интересный проект — Graal.js. То есть часть Graal, ответственная за запуск JavaScript.


Инновационные проекты из мира JDK зачастую воспринимаются чем-то далеким и нереальным. Graal в этом плане отличается — очень внезапно он вышел на сцену как зрелый конкурент.


Graal — это не часть OpenJDK, а отдельный продукт. Он известен тем, что в свежих версиях OpenJDK можно переключить JIT-компилятор из C2 на тот, что идет в составе Graal. Кроме того, в составе Graal поставляется фреймворк Truffle, с помощью которого можно реализовывать разные новые языки. В данном случае разработчики из Oracle Labs реализовали поддержку JavaScript.


Чтобы прочувствовать, насколько это просто и удобно, давайте рассмотрим игрушечный проект-пример.


Представим, что мы делаем рубку НЛО на Хабре.



В первой версии Рубки, НЛО сможет банить рандомных людей, и кнопка будет называться «Забанить кого-нибудь!». Во второй версии кнопка будет банить или троллей, или спамеров, и кого именно мы сейчас баним — будет подгружаться из Java. В целях минимализации примера меняться будет только надпись на кнопке, бизнес-логику прокидывать не будем.


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


Часть 1. Заготовка приложения


1. Качаем «энтерпрайзную» GraalVM (по ссылке) и прописываем обычные для Java переменные окружения.


Энтерпрайзная версия нужна потому, что только в ней есть GraalJS.


Можно, например, в .bash_profile записать вот такое:


graalvm () {
    export LABSJDK=/Users/olegchir/opt/graalvm-0.33/Contents/Home
    export LABSJRE=/Users/olegchir/opt/graalvm-0.33/Contents/Home/jre

    export JDK_HOME=$LABSJDK
    export JRE_HOME=$LABSJRE
    export JAVA_HOME=$JDK_HOME

    export PATH=$JDK_HOME/bin:$JRE_HOME/bin:$PATH
}

И потом после перезагрузки шелла вызвать эту функцию: graalvm.


Почему я предлагаю сделать отдельную баш-функцию и вызывать ее по мере необходимости, а не сразу? Тут всё очень просто: после того, как GraalVM попадет в PATH, ваш нормальный системный npm (например, /usr/local/bin/npm в macOS) будет подменён нашей особой джавовой версией ($JDK_HOME/bin/npm). Если вы JS-разработчик, такая подмена на постоянку — не самая лучшая идея.


2. Делаем директорию для проекта


mkdir -p ~/git/habrotest
cd ~/git/habrotest

3. npm init (заполнить с умом, но можно и просто прощелкать кнопку enter)


4. Устанавливаем нужные модули: Webpack, Babel, React


npm i --save-dev webpack webpack-cli webpack-dev-server
npm i --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react
npm i --save react react-dom

Заметьте, что npm может оказаться слегка устаревшей версии (относительно «настоящего») и попросит обновиться. Обновляться не стоит.


5. Создаем директории, в которых будет происходить работа:


mkdir -p src/client/app
mkdir -p src/client/public
mkdir -p loaders

6. Учим Babel нашим языкам:


./.babelrc:


{
  "presets" : ["es2015", "react"]
}

7. Настраиваем вебпак:


./webpack.config.js:


var p = require('path');
var webpack = require('webpack');

var BUILD_DIR = p.resolve(__dirname, 'src/client/public');
var APP_DIR = p.resolve(__dirname, 'src/client/app');

var config = {
    output: {
        path: BUILD_DIR,
        filename: 'bundle.js'
    },
    entry: APP_DIR + '/index.jsx',
    module : {
        rules : [
            {
                test : /\.jsx?/,
                include : APP_DIR,
                loader : 'babel-loader'
            }
        ]
    }
};

module.exports = config;

8. Создаем страничку для нашего приложения:


./src/client/index.html


<html>
  <head>
    <meta charset="utf-8">
    <title>Добро пожаловать в рубку НЛО</title>
  </head>
  <body>
    <div id="app" />
    <script src="public/bundle.js" type="text/javascript"></script>
  </body>
</html>

9. Создаем индекс (чтобы потом пихать в него демонстрационный компонент):


./src/client/app/index.jsx


import React from 'react';
import {render} from 'react-dom';
import NLOComponent from './NLOComponent.jsx';

class App extends React.Component {
  render () {
    return (
      <div>
        <p>Добро пожаловать в рубку, НЛО</p>
        <NLOComponent />
      </div>
    );
  }
}

render(<App/>, document.getElementById('app'));

10. Создаем компонент!


./src/client/app/NLOComponent.jsx


import React from 'react';

class NLOComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {banned : 0};
    this.onBan = this.onBan.bind(this);
  }

  onBan () {
    let newBanned = this.state.banned + 10;
    this.setState({banned: newBanned});
  }

  render() {
    return (<div>
        Количество забаненных : <span>{this.state.banned}</span>
        <div><button onClick={this.onBan}>Забанить кого-нибудь!</button></div>
      </div>
    );
  }

}

export default NLOComponent;

11. Запускаем сборку: webpack -d


Всё должно успешно собраться и вывести нечто вроде:


joker:habrotest olegchir$ webpack -d
Hash: b19d6529d6e3f70baba6
Version: webpack 4.5.0
Time: 19358ms
Built at: 2018-04-16 05:12:49
    Asset      Size  Chunks             Chunk Names
bundle.js  1.69 MiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/client/app/NLOComponent.jsx] 3.03 KiB {main} [built]
[./src/client/app/index.jsx] 2.61 KiB {main} [built]
    + 21 hidden modules

12. Теперь можно открыть в браузере ./src/client/index.html и насладиться следующим видом:



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


Часть 2. Подсовываем переменные


13. Попробуем внедрить в наш компонент переменную «название кнопки» (buttonCaption) и «список вариантов» (buttonVariants), о которых ничего не известно в JS. В дальнейшем они будут подтягиваться из Java, но сейчас просто проверяем, что их использование приводит к ошибке:


import React from 'react';

class NLOComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {banned : 0, button: buttonCaption};
    this.onBan = this.onBan.bind(this);
  }

  onBan () {
    let newBanned = this.state.banned + 10;
    this.setState({banned: newBanned, 
        button: buttonVariants[Math.round(Math.random())]});
  }

  render() {
    return (<div>
        Количество забаненных : <span>{this.state.banned}</span>
        <div><button onClick={this.onBan}>{this.state.button}</button></div>
      </div>
    );
  }

}

export default NLOComponent;

Наблюдаем честную ошибку:


NLOComponent.jsx?8e83:7 Uncaught ReferenceError: buttonCaption is not defined
    at new NLOComponent (NLOComponent.jsx?8e83:7)
    at constructClassInstance (react-dom.development.js?61bb:6789)
    at updateClassComponent (react-dom.development.js?61bb:8324)
    at beginWork (react-dom.development.js?61bb:8966)
    at performUnitOfWork (react-dom.development.js?61bb:11798)
    at workLoop (react-dom.development.js?61bb:11827)
    at HTMLUnknownElement.callCallback (react-dom.development.js?61bb:104)
    at Object.invokeGuardedCallbackDev (react-dom.development.js?61bb:142)
    at invokeGuardedCallback (react-dom.development.js?61bb:191)
    at replayUnitOfWork (react-dom.development.js?61bb:11302)
(anonymous) @ bundle.js:72

react-dom.development.js?61bb:9627 The above error occurred in the <NLOComponent> component:
    in NLOComponent (created by App)
    in div (created by App)
    in App

14. Теперь давайте познакомимся с легальным способом подсовывать переменные в Вебпаке. Это лоадеры.


Во-первых, нужно немного переписать конфиг вебпака, чтобы удобно грузить кастомные лоадеры:


var p = require('path');
var webpack = require('webpack');
var BUILD_DIR = p.resolve(__dirname, 'src/client/public');
var APP_DIR = p.resolve(__dirname, 'src/client/app');

let defaults = {
    output: { path: BUILD_DIR, filename: 'bundle.js' },
    entry: APP_DIR + '/index.jsx',
    module : { rules : [ { test : /\.jsx?/, include : APP_DIR, loader : 'babel-loader' } ] },
    resolveLoader: { modules: ['node_modules', p.resolve(__dirname, 'loaders')] }
};

module.exports = function (content) {
    let dd = defaults;
    dd.module.rules.push({ test : /index\.jsx/,  loader: "preload", options: {} });
    return dd;
};

(Заметьте, что в options лоадеру можно подсунуть любые данные и потом считать с помощью loaderUtils.getOptions(this) из модуля loader-utils)


Ну и теперь, собственно, пишем лоадер. Лоадер устроен тупо: на вход в параметр source нам приходит изначальный код, мы его изменяем по своему желанию (можем и не изменять) и потом возвращаем назад.


./loaders/preload.js:


const loaderUtils = require("loader-utils"),
    schemaUtils = require("schema-utils");

module.exports = function main(source) {
    this.cacheable();
    console.log("applying loader");

    var initial = "Забанить тролля!";
    var variants = JSON.stringify(["Забанить тролля!", "Забанить спамера!"]);

    return `window.buttonCaption=\"${initial}\";`
            + `window.buttonVariants=${variants};`
            + `${source}`;
};

Выполняем пересборку с помощью webpack -d.


Всё отлично работает, нет никаких ошибок.


Часть 3. Добавляем Java-код


15. Теперь вы спросите: хорошо, мы выучили один маленький грязный хак Вебпака, но при чем здесь Java?


Интересно здесь то, что наш лоадер выполняется не просто так, а под Граалем. Значит, можно с помощью API, похожего на Nashorn'овский, работать из JS с джавовыми типами.


const loaderUtils = require("loader-utils"),
    schemaUtils = require("schema-utils");

module.exports = function main(source) {
    this.cacheable();
    console.log("applying loader");

    //Мы можем получать джавовые типы и содзавать объекты этого типа
    var JavaString = Java.type("java.lang.String");
    var initial = new JavaString("Забанить тролля!");

    //Мы можем конвертить данные туда, сюда, и обратно
    var jsVariants = ["Забанить тролля!", "Забанить спамера!"];
    var javaVariants = Java.to(jsVariants, "java.lang.String[]");
    var variants = JSON.stringify(javaVariants);
    //Но интероп не всегда хорош, и тогда приходится городить костыли

    return `window.buttonCaption=\"${initial}\";`
            + `window.buttonVariants=${variants};`
            + `${source}`;
};

Ну и конечно, webpack -d.


16. При попытке собрать вебпаком видим ошибку:


ERROR in ./src/client/app/index.jsx
Module build failed: ReferenceError: Java is not defined
    at Object.main (/Users/olegchir/git/habrotest/loaders/preload.js:9:19)

Она возникает потому, что джавовые типы недоступны по умолчанию и включаются специальным флагом --jvm, который имеется только в GraalJS, но не в «обычной» Ноде.


Поэтому собирать надо специальной командой:


node --jvm node_modules/.bin/webpack -d


Так как набирать всё это достаточно муторно, я использую алиас в баше. Например, в .bash_profile можно вставить следующую строчку:


alias graal_webpack_build="node --jvm node_modules/.bin/webpack -d"

Или как-нибудь еще короче, чтобы набирать было приятно.


17. PROFIT!


Результат можно посмотреть в моем репозитории на GitHub. Собранные файлы закоммичены прямо в репозиторий, чтобы посмотреть можно было даже не проходя туториал до конца.



Заключение


Вот таким простым и удобным способом мы теперь можем интегрировать Java и JS. Всё это — далеко не единичный случай, способов применения можно придумать множество.


Напоследок, каплю дегтя в бочку меда. В чем же подвох?


  • GraalJS — пока не Open Source, хотя, по слухам, опенсорснуть его хотят; Уже всё в порядке.
  • Джавовый npm пока что подтормаживает. Почему — надо изучать. Тормозит именно npm, а не сам JS-движок;
  • Под капотом у всего этого находится лютая магия, и при попытке туда влезть придется изучать много всего дополнительно;
  • Всё это собрано относительно JDK8. Новых фишек из Java 11 придется дожидаться достаточно долго;
  • Graal — экспериментальный проект. Нужно учитывать это при попытке интегрировать его в совсем уж кровавый энтерпрайз без права на ошибку.

Минутка рекламы. Как вы, наверное, знаете, мы делаем конференции. Ближайшая конференция про JavaScript — HolyJS 2018 Piter, которая пройдет 19-20 мая 2018 года в Санкт-Петербурге. Можно туда прийти, послушать доклады (какие доклады там бывают — описано в программе конференции), вживую пообщаться с практикующими экспертами JavaScript и фронтенда, разработчиками разных моднейших технологий. Короче, заходите, мы вас ждём!

Комментарии (23)


  1. bvn13
    17.04.2018 12:24
    +1

    в пункте 15 я не увидел той магии, о которой было заявлено. в JS объявляются переменные, ну вроде как Java типы… ну а где профит? как данные из Java-сервера передать в клиента? Можешь сделать пример, который будет хотя бы текущую дату из переменной, объявленной в Java модуле на сервере, показывать на странице? или эта тема вообще не связана с клиент-серверным взаимодействием, а просто о том, как можно ноду под граалем запустить?


    1. olegchir Автор
      17.04.2018 12:44

      Эта тема связана не с клиент-серверным взаимодействием, а с тем, что Нода, запущенная на Граале, имеет доступ к джавовским данным.


      Генерация статики с использованием React/Babel/Webpack — более сложная и интересная для джавистов задача, поэтому в статье именно она.


      Вставить данные при генерации HTML со стороны JS-сервера гораздо проще. Выложил работающий пример на гитхабе.


      Всего-то нужно поднять Express и работать с ним так же, как и всегда, цепляя Java по мере необходимости:


      const express = require('express')
      const app = express()
      
      app.get('/', (req, res) => {
          var JavaDate = Java.type("java.util.Date");
          var currDate = new JavaDate();
          res.send(currDate.toString());
      })
      
      app.listen(3000, () => {
          console.log("server started")
      })

      Доставить такие данные на JS-клиента можно так же, как всегда — с помощью аяксовых запросов.


      1. bvn13
        17.04.2018 13:02

        Тогда все это похоже на proof of concept с очень ограниченной областью применения.


        1. olegchir Автор
          17.04.2018 13:16

          Сам Пraal — это не proof of concept, а отличная, хорошо работающая вещь. Над которой работают лучшие в мире специалисты по VM, рантаймам, языкам, и так далее.

          Примеры использования из этой статьи — это действительно, всего лишь примеры. Если начать разводить здесь «правильную архитектуру» и «лучшие практики»- это будет не статя на хабре, а книга страниц на тысячу триста. Когда напишу такую книжку, сможешь купить ее за пять тысяч рублей :-)

          А если говорить философски, мне кажется что органиченность применения — это нормально. Особенно когда ты четко принимаешь эти границы.

          Бесшовный интероп — это особый способ оптимизации, в данном случае он оптимизирует несколько вещей: a) объем труда разработчиков на организацию собственного интеропа б) перфоманс интеропного кода при пересечении границы языка

          Если у тебя в проекте нет мест, где перфоманс просаживается в этом месте, то все замечательно — проходи мимо, чини что-нибудь другое.

          У меня в проектах были и есть такие проблемы (например, как известно, сайты JUG.ru Group создаются статическим генератором).


  1. k12th
    17.04.2018 12:59

    А в чем профит по сравнению с запуском обычного nodejs и вытягиванием нужных данных из Java-приложения по http? Микросервисы и вот это вот всё.


    1. olegchir Автор
      17.04.2018 13:26

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

      Например, у микросервисов безумно дорогая стоимость запроса, а вызов через Graal стоит как обычный джавовый метод. То есть твой вопрос можно довести до абсурда таким вопросом: зачем нам писать в языке программирования какие-то функции и методы, давайте вместо каждого метода вызывать REST-сервис?

      А еще у микросервисов довольно большая стоимость создания, поддержки итп. То есть это обычные вопросы холивора «монолит против микросервисов».

      Есть какая-то минимальная окрестность кода, где вызовы должны быть нативными: это выгодно по перфомансу, по общей стоимости, по чему угодно — в соотетствии со здравым смыслом. И вот уже большие лоскуты соединяются по HTTP. Если когда-то давно переключение контекста языка обязывало начинать медленный интероп (кроме HTTP есть же еще файлы, пайпы, что угодно), то теперь можно не резать на лоскуты то, что должно архитектурно быть единым.

      Имхо, это должно особенно понравиться как раз фуллстек-разработчикам. Когда одна команда пишет бэк, и другая — фронт, это одно, ты всегда можешь скинуть заботу по интеграции на кого-то еще. Когда ты пишешь все сам, от базы данных до анимаций во фронте — то ой, уже задумываешься над более оптимальными решениями. Можно парить мозг подрядчику — но крайне невыгодно парить мозги самому себе.


      1. k12th
        17.04.2018 20:40
        +1

        Аргумент про стоимость интеропа принят. Про интеграцию не совсем согласен, но вас понял.
        Спасибо!


  1. slavap
    17.04.2018 13:12

    Для java -> js спокойно можно взять GWT и не страдать вообще, особенно учитывая, что есть встроенный jsinterop и обёрнутые через него React и Vue.


    1. olegchir Автор
      17.04.2018 13:27

      Можно. Но этот пост — про Graal. Вести аргументированные дебаты про GWT я сейчас не смогу, это отнимает много времени (а мне еще пост о программе DotNext сегодня выпускать!)


      1. sshikov
        17.04.2018 19:34
        +1

        Этот пост про Graal, но тем не менее, мне вот тоже показалось, что местами он слишком узконаправленный. Это взгляд как бы с одной точки зрения, хотя комментарии его конечно уже расширили.

        Делать web приложения на Java реально можно сотней методов, и многие из них ничуть не хуже Node вместе со всем окружением.

        Мне вот всегда было интереснее немного другое — полноценная и легкая интеграция того же Babel, которая на базе Nashorn делается, но скажем прямо, слегка через одно место. А заодно и всего другого инструментария, который уже наваяли вокруг node, и многое из него — совсем даже не про web, или не совсем про web. Скажем, всякие cli инструменты, или d3 какой-нибудь.


        1. olegchir Автор
          17.04.2018 21:28

          В Граале как минимум три проекта: оптимизирующий джит-компилятор, тулсет для создания языков программирования и клозед-ворлд виртуальная машина. Тема про языки программирования делится на подтемы типа гральжс, трюфельруби, сулонг, итп.

          Если писать о каждой из этих тем глубоко и одновременно, то выйдет сочинение с объемами Кнута, это немного неприемлемо для Хабра

          Другие темы тоже будут, но попозже. Слона можно съесть по кусочкам.


          1. sshikov
            17.04.2018 21:33
            +1

            Я догадываюсь, что тема огромна, и это была совсем не претензия, по большому счету. Пишите про грааль еще! Спасибо!


    1. v_m_smith
      18.04.2018 01:21
      +1

      А можно взять Clojure и ClojureScript и Reagent. Интеропа между которыми вообще нет. Т.к. это один и тот же язык :)


  1. VGoudkov
    17.04.2018 18:02
    +1

    Очень больно писать на Java веб-интерфейс (по крайней мере до тех пор, пока JVM и OpenJDK не стабилизируются на WebAssembly)

    Если нужен Web на Java — есть Vaadin. Сайт на сто миллионов клиентов на нём конечно делать не надо, но внутрикопоративное ПО — очень даже.
    PS: demo недоступно, заблокировали наверно вместе с телеграмом


    1. olegchir Автор
      17.04.2018 21:46

      Вопрос как раз в том, что если я не хочу веб на джаве. Джаваскрипт для этого — куда более подходящий инструмент (по крайней мере, пока webassembly вот в этом зачаточном состоянии).


  1. sshikov
    17.04.2018 19:24
    +1

    А кто-нибудь запускал имеющиеся дистрибутивы Graal под той Ubuntu, которая в Windows 10?


    1. olegchir Автор
      18.04.2018 02:43

      Интересный вопрос, надо попробовать.


  1. justboris
    18.04.2018 00:59

    В Node.js надо либо уважать их феншуй по использованию require, либо хакнуть его к чертям вот таким простым кодом

    Что здесь происходит? Это столько костылей из-за нежелания написать if(typeof module === 'object') module.exports = validate?


    В крайнем случае, при очень сильной ненависти к commonjs, всегда можно объявить функцию глобально явным образом. Вот такой код создаст глобальную функцию validate в любом js-окружении:


    (function() {
      this.validate = function(target) { /* ... */ };
    })();


    1. k12th
      18.04.2018 01:03

      сильной ненависти к commonjs

      Что, кстати, было бы странно, т.к. именно из Nashorn commonjs и растет.


      1. justboris
        18.04.2018 09:56

        Только не из Nashorn, а из Rhino, другого JS-интерпретатора на Java.


        Но, в целом, вы правы, commonjs сперва появился совсем не в Node.js


        1. k12th
          18.04.2018 11:44

          Я помнил, что там носорог, но не знал, что их два:)


  1. v_m_smith
    18.04.2018 01:15

    В повседневной практике часто встречаются приложения, состоящие из двух частей: ClojureScript-фронтенд и Clojure-бэкенд. Организация интеропа между ними НЕ требует НИКАКИХ усилий.


  1. Lewik87
    19.04.2018 00:07
    +1

    Я чего то не понимаю. А почему про котлин никто не вспоминает?) Как раз таки на котлине и удобно писать и под jvm и под js. И не надо изобретать велик на js, можно использовать имеющиеся js фреймворки.