В жизни каждого Java разработчика может наступить момент, когда ничего не остаётся, как использовать в своём приложении ReactJS. Если, конечно, не AngularJS. Вы долго сопротивлялись, но этот момент настал и надо что-то делать. Вы слышали, что есть Node.JS, что он умеет быть web-сервером, но это уже через чур. У вас будет кошерный Spring Boot. К сожалению, поиск в гугл способен очень запутать. Много разных гайдов, все оперируют разными версиями реакта и сопутствующих средств. Данный гайд описывает процесс создания простейшего Spring Boot + ReactJS приложения, простой и приятный.

Подготовка среды разработки


Сначала надо скачать и установить Node.js. При установке (требуется наличие административных прав) важно убедиться, что устанавливается npm и прописываются системные пути. В случае Windows появляется «Node.js command prompt», в других OS наверное тоже что-то такое появляется. Запускаем его. Если вы работаете без доступа к интернету (это очень не удобно, но так бывает), вы можете указать пусть к локальному npm-репозиторию; им вполне может быть ваш корпоративный Nexus. Для этого в командной строке выполните команду

npm config set registry http://ваш_npm_репозиторий

Далее мы будем делать классическое maven-приложение. Поскольку статья предназначена прежде всего Java-программистам, рассказывать как это делать, и что указывать в pom.xml, чтобы получилось Spring Boot приложение, я не буду. Создайте папку src\main\resources\static (далее просто static). Перейдите в консоли в неё, и инициализируйте node-приложение, выполнив команду npm init. В интерактивном режиме вам будет предложено ввести основные параметры вашего приложения (название, автор и т.п.). По завершении работы появится файл static\package.json, его можно всегда поменять вручную. Перед выполнением дальнейших шагов крайне желательно обновить менеджер пакетов npm до актуальной версии. Для этого в консоли выполните команду

npm install npm@latest -g

Подключите зависимости, которые вам потребуются. Они делятся на две категории, основные и разработческие — последние не попадут в поставляемую сборку. Минимальный набор для нашего примера такой:

  1. Основные:

    npm install react requirejs react-dom rest --save

  2. Разработческие:

    npm install babel-loader babel-core webpack webpack-dev-server babel-preset-stage-0 babel-plugin-transform-regenerator babel-preset-es2015 babel-preset-react --save-dev

В результате обновится файл static\package.json и создастся папка static\node_modules. Эту папку надо добавить в .gitignore. Также её следует исключить из сборки ресурсов maven'а:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>static/node_modules/**/*.*</exclude>
            </excludes>
        </resource>
    </resources>
</build>

Создание каркаса приложения


О том, как писать приложения на ReacJS вполне доступно написано в аутентичном руководстве от Facebook. Говоря совсем коротко, программирование на ReacJS сводится к тому, что вы пишете обработчики, которые рендерят кастомные тэги в html-код.

Для начала создайте папку static\app, в ней файл app.jsx — там будет основной класс приложения. Добавьте в него следующее содержание:

import React from "react";
import ReactDom from "react-dom";

class App extends React.Component {

render() {
return <p>Hello, World!</p>
    }
}
ReactDom.render(<App />, document.getElementById('react'));

Тут важно следующее: в нашем небольшом классе используется синтаксис JSX, используется синтаксис ES2015 языка JavaScript. Предполагается, что на странице html, куда будет добавлен этот скрипт, есть элемент с id=«react», и именно он будет заменён тэгом <App />, который, в свою очередь, рендерится в сакраментальную надпись. Далее создаём файл static\index.js со следующим содержимым:

import App from './app/app.jsx';

Это будет точка входа в наше приложение. Если вы используете Idea, и она после этого ругается, что текущая версия JavaScript не поддерживает импорты, жмёте Alt-Enter и включаете ES6.

Наконец, создайте файл src\mail\resources\templates\greeting.html со следующим содержимым:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" http-equiv="CONTENT-TYPE" content="text/html; charset=UTF-8"/>
    <title>Hello demo</title>
</head>
<body>
    <div id="react"/>
    <script src="/generated/app-bundle.js"/>
</body>
</html>

Собственно это всё в части программирования. Если вы используете Spring MVC, вам не составит труда сделать приложение, которое покажет этот html. Осталось только преобразовать наш JSX скрипт в годный JavaScript, и всё взлетит. Для этого мы будем использовать Webpack.

Настройка компиляции с Webpack


Использование Webpack совместно с ReactJS является распространённой практикой. Грубо говоря, процесс сводится к тому, что Webpack, используя различные библиотеки babel транслирует наш код в такой, который поймёт браузер. При этом нам было бы очень приятно, чтобы в нём можно было ставить брекпойнты, и не в какое-то сгенерённое не пойми что, а в исходный понятный JSX скрипт. К счастью, эта задача разрешима. Для успешного выполнения дальнейших шагов предполагается, что рекомендованные выше обязательные и девелоперские зависимости были подключены, и ваш файл package.json в этой части имеет примерно следующий вид:

"dependencies": {
  "react": "^15.4.2",
  "react-dom": "^15.4.2",
  "requirejs": "^2.3.2",
},
"devDependencies": {
  "babel-core": "^6.22.1",
  "babel-loader": "^6.2.10",
  "babel-plugin-transform-regenerator": "^6.22.0",
  "babel-preset-es2015": "^6.22.0",
  "babel-preset-react": "^6.22.0",
  "babel-preset-stage-0": "^6.22.0",
  "react-frame-component": "^0.6.6",
  "webpack": "^2.2.1",
  "webpack-dev-server": "^2.3.0"
}

Важно, что мы используем Webpack2, с первым всё по-другому. Создайте файл static\.babelrc со следующим содержанием (как не трудно догадаться, это конфигурация Babel):

{
  "presets": ["react", "es2015", "stage-0"],
  "plugins": ["transform-regenerator"]
}

Создайте файл static\webpack.config.js со следующим содержимым:
var packageJSON = require('./package.json');
var path = require('path');
var webpack = require('webpack');
module.exports = {
    devtool: 'source-map',
    entry: './index.js',
    output: {
        path: path.join(__dirname, 'generated'),
        filename: 'app-bundle.js'},
    resolve: {extensions: ['.js', '.jsx']},
    plugins: [
        new webpack.LoaderOptionsPlugin({
            debug: true}),
        new webpack.DefinePlugin({
            "process.env": {
                NODE_ENV: JSON.stringify("development")
            }
        })
    ],
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/
	       }
        ]
    },
    devServer: {
        noInfo: false,
        quiet: false,
        lazy: false,
        watchOptions: {
            poll: true
       }
    }
}

Обратите внимание, что:

  1. Точкой входа является ранее определённый скрипт index.js
  2. Результат компиляции будет положен в папку static\generated
  3. Настройки установлены разработческие. В дальнейшем имеет смысл использовать профили webpack, разделяя конфигурации по средам dev\test\prod. За подробностями отсылаю к документации (ссылка ниже)
  4. Результирующий скрипт будет называться "/generated/app-bundle.js". Именно его мы указали в greeting.html.

Добавьте в файл package.json раздел:
"scripts": {
  "build": "webpack -d"
},

Ключ "-d" означает «development», если использовать -p «production», то результат будет минифицирован. В принципе, это не существенно, на возможность дебажить это не влияет. Выполните компиляцию, запустив в командной строке Node.js команду npm run build. Добавьте в .gitignore npm-*.log и generated/.

Обратите внимание, что даже в таком минимальной примере компиляция webpack занимает несколько секунд (у меня до 8с). Это много. Имеет смысл настроить автоматическую перекомпиляцию. С этой целью был ранее установлен пакет webpack-dev-server, и добавлены его конфигурационные параметры в секции devServer. Данная задача реализуется как фоновый процесс, запускаемый скриптом. Чтобы этот скрипт запустить, нужно добавить в секцию scripts файла package.json элемент "«dev»: «webpack-dev-server --content-base app/»" (здесь «dev» — алиас скрипта, можно использовать любой). Этим мы настраиваем процесс мониторинга изменений в папке app/, в которой лежит наш JSX скрипт.



Затем выполните команду npm run dev. В результате запустится Node.js Express сервер, который сначала выполнит обычную компиляцию webpack, закэширует всё, что нужно, и затем будет при каждом изменении инкрементально перекомпиливать. У этого сервера довольно много опций, отсылаю к оригинальной документации (ссылка ниже). Если требуется выполнять ещё какие-то действия в командной строке npm, придётся запустить новую, эта теперь занята.

И теперь совсем всё. Запускаете приложение и оно радуется миру.

И ещё кое-что


Для полноты картины надо упомянуть, что можно обойтись и без консоли. Для работы с npm существует довольно удобный maven-плагин frontend-maven-plugin. Фрагмент pom-файла с его участием приведён ниже:

Подключение плагинов maven
<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>1.4.2.RELEASE</version>
    </plugin>

    <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>1.0</version>
        <configuration>
            <workingDirectory>src/main/resources/static</workingDirectory>
        </configuration>
        <executions>
            <execution>
                <id>install node and npm</id>
                <goals>
                    <goal>install-node-and-npm</goal>
                </goals>
                <configuration>
                    <nodeVersion>v6.9.4</nodeVersion>
                    <npmVersion>4.1.1</npmVersion>
                </configuration>
            </execution>
            <execution>
                <id>npm install</id>
                <goals>
                    <goal>npm</goal>
                </goals>
                <configuration>
                    <arguments>install</arguments>
                    <npmRegistryURL>http://some_url>
                </configuration>
            </execution>
            <execution>
                <id>npm run build</id>
                <goals>
                    <goal>npm</goal>
                </goals>
                <configuration>
                    <arguments>run build</arguments>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>


Полезные ссылки


  1. Аутентичное руководство по ReactJS
  2. Отличный туториал от Spring
  3. Туториал по Webpack
  4. Конфигурирование webpack-dev-server
Поделиться с друзьями
-->

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


  1. dmitryvim
    10.02.2017 13:12
    +1

    Хотелось бы побольше услышать про backend и вопросы авторизации. Хотя, судя по тому, что вы используете spring-data-rest, backend работает только как хранилище?


  1. kmorozov
    10.02.2017 13:13

    Это осталось за кадром, поскольку статья скорее про «React для java программистов», но в планах немного продолжить, и показать более сложный пример React + Spring Security + iFrame + RememberMe + MongoDB.


    1. EaE
      10.02.2017 17:20
      +1

      Было бы очень круто! Потому что между реактом и спрингом еще должен быть какой-то код для обмена данными, и именно его очень хотелось увидеть в этом посте, так хотелось, что аж до конца дочитал, а концовка как в «Борн: Эволюция» оказалась.


    1. olegchir
      11.02.2017 09:24

      да, отчаянно плюсую.
      у нас есть такие приложения (java+react), при выборе как делать — путей было множество
      интересно увидеть как делают еще люди
      особенно рассказы какие проблемы собрали те, кто пускают ноду и жс внутри джавы


      1. ptnk
        11.02.2017 21:35

        Поделюсь своим опытом использования Spring + ReactJs.

        Своё знакомство начал на проекте с gulp + browserify + reactjs + jee.
        Несколько тасков в gulp:
        — прод собирает бандл, минифицирует
        — вариант сборки в папку application server
        — вариант сборки с 'watch', когда мы запускаем процесс, а по ходу изменения в коде js -> сборочка автоматически кладётся в папку application server.

        В итоге: работаем через watch, когда автоматом подсасывает изменения на сервер или руками запускаем таск.

        Второй проект:
        Webpack + ReactJs + Spring
        — Один отдельный проект для java-script
        — Второй отдельный проект для java

        Имеется конфигурация для prod (сборка, минификация). Скриптом запускается процесс сборки, после сборки собранные файлики кладутся по месту дислокации в java.
        [Запуск буквально обычным npm run build]

        И имеется вариант для debug-разработки. (Включенные source-map). Смысл в том, что при каждом изменении сборка пересобирается, кладётся на сервер, В браузере можно дебажить js-код.
        [Запуск с webpack --config ./webpack.dev.js --progress --watch]
        Здесь пришлось пожертвовать плагином, который при пересборки рефрешил компоненты.

        Есть ещё третий вариант, когда мы запускаем ноду в качестве сервера и с нею мы разрабатываем фронтэнд. Но в итоге отказался от этого, т.к. нужна зацепка за реально работающие rest-сервисы.


        1. olegchir
          15.02.2017 09:19

          Блин, поздно заметил комментарий и не могу лайкнуть уже.

          У меня такое ощущение, что это нужно всё оформлять в виде набора спринговых бинов, и открывать проект типа Spring Cloud Reactive

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


  1. taujavarob
    10.02.2017 18:14

    Слишком коротко написано. Имхо.


  1. Sapporo
    10.02.2017 21:36

    Спасибо, было познавательно


  1. r0zh0k
    12.02.2017 01:35
    +1

    Я так понимаю – суть статьи в том, как ложить/собирать сорцы фронтенда в java-проекте?
    Зачем это? Могу понять кейс только если вы где-то используете MVC+шаблонизатор для вставки каких-то больших динамических кусков, только при чем тут React тогда?:)

    Иначе, как мне кажется, правильно организовать все следующим образом:
    1. Spring Boot торчит наружу rest-api который используется фронтендом.
    2. Фронтенд находится в отдельном проекте, собирается отдельно чем душе угодно, итоговые js/css ассеты ложатся на CDN.
    3. Spring Boot отдает наружу index в котором есть ровно две ссылки – на cdn с js и css.
    4. Крайне желательно сразу настроить cache bust для всех ассетов чтобы избежать проблем с CDN и кеширующими прокси тут и там. Для бекенда сделать какие-то dynamic properties чтобы при обновлении отдавалось то что нужно.


  1. taujavarob
    13.02.2017 16:43

    Создайте файл static\.babelrc со следующим содержанием (как не трудно догадаться, это конфигурация Babel):


    Лайфак:
    If you’re writing a Babel beginner tutorial please use package.json instead of .babelrc. Hidden files are so easy to miss.(Dan Abramov)

    image