«1. Обзор

В этом уроке мы поймем, что такое изоморфное приложение. Мы также обсудим Nashorn, движок JavaScript в комплекте с Java.

Кроме того, мы рассмотрим, как мы можем использовать Nashorn вместе с внешней библиотекой, такой как React, для создания изоморфного приложения.

2. Немного истории

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

Браузер Netscape появился с поддержкой JavaScript еще в середине девяностых. Это начало смещать часть обработки с серверной стороны на клиентский браузер. Долгое время разработчики боролись с различными проблемами, связанными с поддержкой JavaScript в веб-браузерах.

С растущим спросом на более быстрый и интерактивный пользовательский интерфейс границы уже раздвинулись. Одним из первых фреймворков, изменивших правила игры, был jQuery. Он принес несколько удобных для пользователя функций и значительно расширил поддержку AJAX.

Вскоре стало появляться множество фреймворков для фронтенд-разработки, что значительно улучшило опыт разработчика. Начиная с AngularJS от Google, React от Facebook и позже Vue, они начали привлекать внимание разработчиков.

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

Захватывающий опыт на все более быстрых портативных устройствах требует большей обработки на стороне клиента.

3. Что такое изоморфное приложение?

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

Однако можно также использовать ту же структуру на стороне сервера и создать такой же пользовательский интерфейс.

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

У этого подхода есть преимущества, которые мы обсудим позже.

Такие веб-приложения называются изоморфными или универсальными. Теперь языком клиентской части является исключительно JavaScript. Следовательно, чтобы изоморфное приложение работало, мы также должны использовать JavaScript на стороне сервера.

Node.js на сегодняшний день является наиболее распространенным выбором для создания приложения с визуализацией на стороне сервера.

4. Что такое Нашорн?

Итак, как же подходит Nashorn и почему мы должны его использовать? Nashorn — это движок JavaScript, по умолчанию упакованный с Java. Следовательно, если у нас уже есть серверная часть веб-приложения на Java и мы хотим создать изоморфное приложение, Nashorn очень удобен!

Nashorn был выпущен как часть Java 8. Он в первую очередь ориентирован на разрешение встроенных приложений JavaScript в Java.

Nashorn компилирует JavaScript в памяти в Java Bytecode и передает его JVM для выполнения. Это обеспечивает лучшую производительность по сравнению с более ранним двигателем Rhino.

5. Создание изоморфного приложения

Мы рассмотрели достаточно контекста. Наше приложение здесь будет отображать последовательность Фибоначчи и предоставлять кнопку для генерации и отображения следующего числа в последовательности. Теперь давайте создадим простое изоморфное приложение с внутренним и внешним интерфейсом:

Внешний интерфейс: простой внешний интерфейс на основе React.js Серверный интерфейс: простой внутренний интерфейс Spring Boot с Nashorn для обработки JavaScript ~ ~~ 6. Внешний интерфейс приложения

    Мы будем использовать React.js для создания внешнего интерфейса. React — это популярная библиотека JavaScript для создания одностраничных приложений. Это помогает нам разложить сложный пользовательский интерфейс на иерархические компоненты с необязательным состоянием и односторонней привязкой данных.

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

6.1. Компонент React

Давайте создадим наш первый компонент React:

«

«Теперь давайте разберемся, что делает приведенный выше код:

var App = React.createClass({displayName: "App",
    handleSubmit: function() {
    	var last = this.state.data[this.state.data.length-1];
    	var secondLast = this.state.data[this.state.data.length-2];
        $.ajax({
            url: '/next/'+last+'/'+secondLast,
            dataType: 'text',
            success: function(msg) {
                var series = this.state.data;
                series.push(msg);
                this.setState({data: series});
            }.bind(this),
            error: function(xhr, status, err) {
                console.error('/next', status, err.toString());
            }.bind(this)
        });
    },
    componentDidMount: function() {
    	this.setState({data: this.props.data});
    },	
    getInitialState: function() {
        return {data: []};
    },	
    render: function() {
        return (
            React.createElement("div", {className: "app"},
            	React.createElement("h2", null, "Fibonacci Generator"),
            	React.createElement("h2", null, this.state.data.toString()),
                React.createElement("input", {type: "submit", value: "Next", onClick: this.handleSubmit})
            )     
        );
    }
});

Для начала мы определили компонент класса в React под названием «App». Самая важная функция внутри этого компонента — «render», которая отвечает за генерацию пользовательский интерфейс Мы предоставили имя класса стиля, которое может использовать компонент. Мы используем здесь состояние компонента для хранения и отображения серии. Хотя состояние инициализируется как пустой список, оно извлекает данные, переданные компоненту в качестве реквизита, когда компонент монтируется Наконец, по нажатию кнопки «Добавить» выполняется jQuery-вызов службы REST. Вызов извлекает следующее число в последовательности и добавляет его к состоянию компонента. Изменение состояния компонента автоматически повторно отображает компонент.

    6.2. Использование компонента React

React ищет именованный элемент «div» на странице HTML, чтобы закрепить его содержимое. Все, что нам нужно сделать, это предоставить HTML-страницу с этим элементом «div» и загрузить файлы JS:

Итак, давайте посмотрим, что мы здесь сделали:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello React</title>
    <script type="text/javascript" src="js/react.js"></script>
    <script type="text/javascript" src="js/react-dom.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
    ReactDOM.render(
        React.createElement(App, {data: [0,1,1]}),
        document.getElementById("root")
    );
</script>
</body>
</html>

Мы импортировали необходимые библиотеки JS, react, react-dom и jQuery. После этого мы определили элемент «div» с именем «root». Мы также импортировали файл JS с нашим компонентом React. Затем мы назвали компонент React «App» с некоторыми начальными данными, первые три числа Фибоначчи

    7. Серверная часть приложения

Теперь давайте посмотрим, как мы можем создать подходящую внутреннюю часть для нашего приложения. Мы уже решили использовать Spring Boot вместе с Spring Web для создания этого приложения. Что еще более важно, мы решили использовать Nashorn для обработки внешнего интерфейса на основе JavaScript, который мы разработали в предыдущем разделе.

7.1. Зависимости Maven

Для нашего простого приложения мы будем использовать JSP вместе с Spring MVC, поэтому мы добавим пару зависимостей в наш POM:

Первая — это стандартная зависимость загрузки Spring для веб-приложение. Второй нужен для компиляции JSP.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

7.2. Веб-контроллер

Давайте теперь создадим наш веб-контроллер, который будет обрабатывать наш файл JavaScript и возвращать HTML, используя JSP:

Итак, что именно здесь происходит:

@Controller
public class MyWebController {
    @RequestMapping("/")
    public String index(Map<String, Object> model) throws Exception {
        ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
        nashorn.eval(new FileReader("static/js/react.js"));
        nashorn.eval(new FileReader("static/js/react-dom-server.js"));
        nashorn.eval(new FileReader("static/app.js"));
        Object html = nashorn.eval(
          "ReactDOMServer.renderToString(" + 
            "React.createElement(App, {data: [0,1,1]})" + 
          ");");
        model.put("content", String.valueOf(html));
        return "index";
    }
}

Мы получаем экземпляр ScriptEngine типа Nashorn из ScriptEngineManager. Затем мы загружаем соответствующие библиотеки в React, react.js и react-dom-server.js. Мы также загружаем наш JS-файл, в котором есть наш реагирующий компонент «App». Наконец, мы оцениваем JS-фрагмент, создающий реагирующий элемент, с помощью компонент «App» и некоторые начальные данные Это дает нам выходные данные React, фрагмент HTML как объект. Мы передаем этот фрагмент HTML как данные в соответствующее представление — JSP

    7.3. JSP

Теперь, как нам обработать этот HTML-фрагмент в нашем JSP?

Вспомните, что React автоматически добавляет свой вывод к именованному элементу «div» — в нашем случае «root». Однако мы добавим сгенерированный на стороне сервера HTML-фрагмент к тому же элементу вручную в нашем JSP.

Давайте посмотрим, как теперь выглядит JSP:

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

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello React!</title>
    <script type="text/javascript" src="js/react.js"></script>
    <script type="text/javascript" src="js/react-dom.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root">${content}</div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
	ReactDOM.render(
        React.createElement(App, {data: [0,1,1]}),
        document.getElementById("root")
    );
</script>
</body>
</html>

7.4. Контроллер REST

Наконец, нам также нужна конечная точка REST на стороне сервера, которая дает нам следующее число Фибоначчи в последовательности:

Здесь ничего особенного, просто простой контроллер Spring REST.

@RestController
public class MyRestController {
    @RequestMapping("/next/{last}/{secondLast}")
    public int index(
      @PathVariable("last") int last, 
      @PathVariable("secondLast") int secondLast) throws Exception {
        return last + secondLast;
    }
}

8. Запуск приложения

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

Мы должны запустить приложение Spring Boot в обычном режиме, используя класс начальной загрузки:

Когда мы запускаем этот класс, Spring Boot компилирует наши JSP и делает их доступными на встроенном Tomcat вместе с остальной частью Интернета. применение.

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

Теперь, если мы зайдем на наш сайт, мы увидим:

Давайте разберемся в последовательности событий:

«Браузер запрашивает эту страницу. Когда поступает запрос на эту страницу, веб-контроллер Spring обрабатывает JS-файлы. Механизм Nashorn создает фрагмент HTML и передает его в JSP. JSP добавляет этот фрагмент HTML в «корневой» элемент div, возвращая, наконец, приведенный выше код. HTML-страница Браузер отображает HTML, тем временем начинает загрузку JS-файлов Наконец, страница готова для действий на стороне клиента — мы можем добавить больше чисел в последовательность

Здесь важно понимать, что произойдет, если React найдет фрагмент HTML в целевом элементе «div». В таких случаях React сравнивает этот фрагмент с тем, что у него есть, и не заменяет его, если находит разборчивый фрагмент. Это именно то, что обеспечивает рендеринг на стороне сервера и изоморфные приложения.

    9. Что еще возможно?

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

Мы создали только один компонент React в нашем приложении, тогда как на самом деле это может быть несколько компонентов, образующих иерархию, которые передают данные через свойства. например, создавать отдельные JS-файлы для каждого компонента, чтобы управлять ими и управлять их зависимостями с помощью «экспорта/требования» или «экспорта/импорта». Кроме того, может оказаться невозможным управлять состоянием только внутри компонентов; мы можем захотеть использовать библиотеку управления состоянием, такую ​​​​как Redux. Кроме того, нам может потребоваться взаимодействие с внешними службами в качестве побочных эффектов действий; это может потребовать от нас использования такого шаблона, как redux-thunk или Redux-Saga. Самое главное, мы хотели бы использовать JSX, расширение синтаксиса JS для описания пользовательского интерфейса

Хотя Nashorn полностью совместим с чистым JS, он может не поддерживает все функции, упомянутые выше. Многие из них требуют транскомпиляции и полифиллов из-за совместимости с JS.

    Обычной практикой в ​​таких случаях является использование сборщика модулей, такого как Webpack или Rollup. В основном они обрабатывают все исходные файлы React и объединяют их в один файл JS вместе со всеми зависимостями. Это неизменно требует современного компилятора JavaScript, такого как Babel, для компиляции JavaScript с обратной совместимостью.

В финальном пакете есть только старый добрый JS, который понимают браузеры и которого придерживается Nashorn.

10. Преимущества изоморфных приложений

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

10.1. Рендеринг первой страницы

Одним из наиболее значительных преимуществ изоморфного приложения является более быстрый рендеринг первой страницы. В типичном клиентском приложении браузер начинает с загрузки всех артефактов JS и CSS.

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

10.2. Оптимизация для SEO

Еще одно преимущество, часто упоминаемое в отношении рендеринга на стороне сервера, связано с SEO. Считается, что поисковые боты не могут обрабатывать JavaScript и, следовательно, не видят индексную страницу, отображаемую на стороне клиента с помощью таких библиотек, как React. Таким образом, страница, отображаемая на стороне сервера, более удобна для SEO. Однако стоит отметить, что современные поисковые роботы утверждают, что обрабатывают JavaScript.

11. Заключение

В этом руководстве мы рассмотрели основные понятия изоморфных приложений и движка Nashorn JavaScript. Далее мы рассмотрели, как создать изоморфное приложение с помощью Spring Boot, React и Nashorn.

Затем мы обсудили другие возможности расширения клиентского приложения и преимущества использования изоморфного приложения.

Как всегда, код можно найти на GitHub.

«

As always, the code can be found over on GitHub.