Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Java Legacy to React.js Migration

React Moscow
December 11, 2019

From Java Legacy to React.js Migration

Oleg Korovin, Odnoklassniki @ React Moscow Meetup 5
Dec 11, 2019, Gazprombank

Доклад про то, как Одноклассники решили начать миграцию со старого стека на Реакт, и какие при этом были трудности, с которыми пришлось справляться. Невероятная история запуска Реакта в мире Java.

React Moscow

December 11, 2019
Tweet

More Decks by React Moscow

Other Decks in Programming

Transcript

  1. !1

  2. О себе • 15 лет во фронтенде • «Вроде знаю

    JS» • Сайт аэропорта Пулково • Студия Лебедева, Яндекс, Одноклассники • Не делаю opensouce !2
  3. !5

  4. !7

  5. Проблемы • Старые библиотеки • Нет единого фреймворка • Нет

    изоморфности • Нет единого структурированного приложения на клиенте • Плохая отзывчивость • Недостаточный инструментарий • Большой порог входа • … !8
  6. Требования к решению •Изоморфный код для UI •Плавный переход (миграция)

    •Серверный рендеринг •У нас не должно быть усложнения эксплуатации !9
  7. !19

  8. Как работает сервер сейчас !21 < 200ms one-nio - бинарный

    протокол 
 https://github.com/odnoklassniki/one-nio HTTP one-nio < 1ms HTML WEB API
  9. !23

  10. !24

  11. Почему не NodeJS !26 •HTTP транспорт - медленно •Cериализация/десериализация -

    доп нагрузка, задержка, сложность •Еще один компонент - риски эксплуатации
  12. WEB А можно ли запустить JS в JVM? !27 •

    Простая инфраструктура • Нет накладных расходов
  13. Nashorn Rhino GraalVM JS рантаймы в JVM !29 • Всё

    на JVM • Java-JS взаимодействие • Безопасный рантайм • Компилятор на Java
  14. !32

  15. GraalVM + React • Нет поддержки типов в JS •

    Риски эксплуатации GraalVM - !34 • Java + JS • Сообщество • Низкий порог входа • Эксплуатация не усложнилась • Найм сотрудников +
  16. JS Context !37 JS Context JAVA global Context context =

    Context.create("js"); //получаем global данного контекста Value js = context.getBindings("js"); Context context = Context.create("js"); //получаем global данного контекста Value js = context.getBindings("js");
  17. Context context = Context.create("js"); //получаем global данного контекста Value js

    = context.getBindings("js"); //можем читать из global Value load = js.getMember("load"); //можем вызвать функцию из контекста load.execute(pathToModule); //можем выполнить код в данном контексте context.eval("js", someCode); //можем читать из global Value load = js.getMember("load"); //можем вызвать функцию из контекста load.execute(pathToModule); //можем выполнить код в данном контексте context.eval("js", someCode); JS Context !38 JAVA JS Context global load() app.js eval()
  18. JS Context !39 Context context = Context.create("js"); //получаем global данного

    контекста Value js = context.getBindings("js"); //можем читать из global Value load = js.getMember("load"); //вызываем функцию из контекста load.execute(pathToModule); //можем выполнить код в данном контексте context.eval("js", someCode); //можем записать в global js.putMember("serverProxy", serverProxy); //можем записать в global js.putMember("serverProxy", serverProxy); //можем читать из global Value app = js.getMember("app"); JAVA JS Context global load() app.js eval() js data java data
  19. WEB !42 Серверный рендеринг JS - концепт HTTP HTML API

    data data HTML Шаблонизатор render(data)
  20. WEB !44 Серверный рендеринг JS - реализация HTTP HTML API

    data Шаблонизатор Очередь <custom-tag /> Java Thread Pool HTML
  21. CPU CPU CPU CPU Почему пул контекстов - это хорошо?

    !45 Java Thread Pool Java data Thread Thread Thread Thread
  22. Движок приложения !51 •Инициализирует приложение •Связывает приложение с дом узлом

    •Предоставляет •единое апи для рендеринга и уничтожения приложения •сервисы локализации, настроек, пр..
  23. Код приложения !52 export class EventsCalendarBlock extends ReactAppBlock<...> { //

    Создаём реализацию API блока getAppAPI() { return { setDate: (date) => this.ctrl.store.setDate(date), ... }; } // Возвращаем правила парсинга атрибутов getAttrsTypes() { return { date: AttrsType.STRING, ... }; } getAppEntities() { return { Store: EventsCalendarStore, Controller: EventsCalendarController, Component: EventsCalendar }; } }
  24. Код приложения !53 export class EventsCalendarBlock extends ReactAppBlock<...> { //

    Создаём реализацию API блока. getAppAPI() { return { setDate: (date) => this.ctrl.store.setDate(date), ... }; } // Возвращаем правила парсинга атрибутов getAttrsTypes() { return { date: AttrsType.STRING, ... }; } getAppEntities() { return { Store: EventsCalendarStore, Controller: EventsCalendarController, Component: EventsCalendar }; } } getAppEntities() { return { Store: EventsCalendarStore, Controller: EventsCalendarController, Component: EventsCalendar }; } // Возвращаем правила парсинга атрибутов getAttrsTypes() { return { date: AttrsType.STRING, ... }; } // Создаём реализацию API блока getAppAPI() { return { setDate: (date) => this.ctrl.store.setDate(date), ... }; }
  25. Как запускать код в GraalVM? !62 •Транспайлить TS в JS,

    как для NodeJs •Собирать вебпаком, как для клиента
  26. Как надо собирать !69 • Возможность сконфигурировать отдельно сборку каждой

    из частей • Проставить зависимости между ними • Собирать все за 1 раз
  27. Стадии сборки Webpack Активно применяются 1. HashedModuleIdsPlugin: При сборке бандла

    в качестве идентификатора модуля используется хэш, вместо инкрементирующегося счётчика. 
 2. DllPlugin: В процессе сборки генерируется файл-манифест, содержащий список экспортируемых классов данной стадии
 3. DllReferencePlugin: Используя файл-манифест предыдущей стадии(-й) позволяет импортировать общий класс
 4. webpack-merge: Позволяет склеивать конфигурацию из множества различных кусочков 0я стадия сборки. Сторонние библиотеки. • 0/dll.js — Параметры DllPlugin, используемые на этой стадии • 0/stage.js — Базовая конфигурация • 0/vendors.js — Список сторонних библиотек, включаемых в бандл • 0/entries.js — Комбинирует кусочки для webpack-merge 1я стадия сборки. Базовые классы (зависимые от среды выполнения). • 1/dll.js — Параметры DllPlugin, используемые на этой стадии • 1/stage.js — Расширение конфигурации • 1/core.js — Список базовых классов • 1/core-server.js — Бандл классов для работы на сервере (GraalJS) • 1/core-client.js — Бандл классов для работы на клиенте (Браузер) • 1/entries.js — Комбинирует кусочки для webpack-merge 2я стадия сборки. Проектные классы. • 2/stage.js — Расширение конфигурации • 2/projects.js — Список генерируемых бандлов • 2/plugins.js — Список используемых плагинов • 2/loaders.js — Список используемых загрузчиков • 2/entries.js — Комбинирует кусочки для webpack-merge !72
  28. Методы Java для JS public class ServerMethods { ... /**

    * Получаем текст в виде строки */ public String getText(String pkg, String key) { ... } ... } !76
  29. Методы Java для JS !77 //добавляем объект с методами Java

    в поле контекст js.putMember("serverMethods", serverMethods);
  30. Методы Java в JS function getText(key: string): string { return

    global.serverMethods.getText(pkg, key); } !78
  31. Результат работы webpack плагина { "some-tag": { "pkg": [ "smiles",

    "sadness" ], "cfg": [ "config1", "config2" ], "bundleName": "some-tag", "js": "some-tag.js", "css": "some-tag.css" } } !83 •Watcher
  32. Старый код в новом !87 • Нет активации, деактивации •

    Разные жизненные циклы • Одновременное взаимодействие • Прокидывание на сервере Какие есть проблемы старый движок новое приложение старый код
  33. export class OldCodeBase<T> extends React.Component<T> { el: HTMLElement | null

    = null; ref = (el: HTMLElement | null) => (this.el = el); componentDidMount() { this.props.ctrl.activate(this.el); // ЗАПУСК активации } componentWillUnmount() { this.props.ctrl.deactivate(this.el); // ЗАПУСК деактивации } shouldComponentUpdate() { // Запрещаем обновление компонента, //чтобы не было проблемы повторной активации модуля return false; } render() { return ( <div ref={this.ref}></div> ) } } Запуск старого кода в новом !88
  34. export class OldCodeBase<T> extends React.Component<T> { el: HTMLElement | null

    = null; ref = (el: HTMLElement | null) => (this.el = el); componentDidMount() { this.props.ctrl.activate(this.el); // ЗАПУСК активации } componentWillUnmount() { this.props.ctrl.deactivate(this.el); // ЗАПУСК деактивации } shouldComponentUpdate() { // Запрещаем обновление компонента, //чтобы не было проблемы повторной активации модуля return false; } render() { return ( <div ref={this.ref}></div> ) } } Запуск старого кода в новом !89 render() { return ( <div ref={this.ref}></div> ) } shouldComponentUpdate() { // Запрещаем обновление компонента, //чтобы не было проблемы повторной активации модуля return false; } componentDidMount() { this.props.ctrl.activate(this.el); // ЗАПУСК активации } componentWillUnmount() { this.props.ctrl.deactivate(this.el); // ЗАПУСК деактивации } el: HTMLElement | null = null; ref = (el: HTMLElement | null) => (this.el = el);
  35. render() { return ( <div> <UiPart id="old-code" /> </div> );

    } Вставка старого кода на сервере !92
  36. export class UiPart extends OldCodeBase<IUiBodyProps> { render() { const id

    = this.props.id; const parts = this.props.parts; if (!parts.hasOwnProperty(id)) { return null; } return React.createElement('ui-part', { 'data-part-id': id, ref: this.ref, dangerouslySetInnerHTML: { __html: parts[id] } }); } } Код компонента !95 return React.createElement('ui-part', { 'data-part-id': id, ref: this.ref, dangerouslySetInnerHTML: { __html: parts[id] } }); export class UiPart extends OldCodeBase<IUiBodyProps> { render() { const id = this.props.id; const parts = this.props.parts; if (!parts.hasOwnProperty(id)) { return null; } return React.createElement('ui-part', { 'data-part-id': id, ref: this.ref, dangerouslySetInnerHTML: { __html: parts[id] } }); } }
  37. !96

  38. Что удалось сделать !99 • Запустить JS на сервере, там

    где это казалось невозможным ) • Можем писать изоморфный код для UI • Начали использовать современный стек для фронтенда, со всеми его плюсами • Появилась единая современная платформа для создания UI • Начали плавный переход (миграцию)