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

From Java Legacy to React.js Migration

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for React Moscow React Moscow
December 11, 2019

From Java Legacy to React.js Migration

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

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

Avatar for React Moscow

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 • Начали плавный переход (миграцию)