Всем привет! Меня зовут Петр Солопов, я руководитель отдела фронтенд-разработки в SuperJob. Думаю, многие из вас видели популярную серию картинок в интернете про фронтенд и бэкенд: на бекенде всегда какой-то монстр, а на фронте — все мило, летают бабочки. На мой взгляд, это не соответствует действительности и все не так радужно и безоблачно: чего только стоят настройка Webpack, тонна зависимостей, особенности фреймворков и многое другое. За подробностями под кат.

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

Есть много статей, и вот одна из них, в которой говорится об основных недостатках React:
Из коробки он не работает в браузере из-за JSX, т.к. это проприетарный синтаксис, невалидный для браузера. А писать, не используя JSX, на React практически невозможно.
Для «Hello, World» команда React предлагает инструмент create-react-app, который загружает на вашу машину 2,5 миллиона строк кода зависимостей на JavaScript.
Теперь React не просто библиотека для рендринга. В нем есть такие фичи, как server components, и другие.
Что не так с современным фронтендом?
Так как React — самый популярный фреймворк, то все его проблемы в той или иной степени специфичны для фронтенда в целом:
Корпорации продвигают проприетарные вещи, а не развивают стандарты — «Hello, World» на любом популярном фреймворке не работает в браузере;
Колоссальное количество зависимостей в приложениях, сложности настройки сборки.
Об этих проблемах также упоминается в статьях (например, I don't want to do front-end anymore), которые становятся довольно популярными в сообществе.
It was easy to get started, too — you just created the files and refreshed the page.
Как это можно исправить
React и другие популярные фреймворки были выпущены в районе 2013 года. Тогда даже не было ES2015. Давайте посмотрим, какие на текущий момент есть инструменты, которые смогут решить вышеописанные проблемы. Первый инструмент, о котором хочется сказать, это нативные модули для JavaScript — ES-modules.
ES-modules
ES-модули приносят в JavaScript официальную унифицированную модульную систему. Однако, чтобы прийти к этому, потребовалось около 10 лет работы по стандартизации. Сегодня почти все основные браузеры из коробки поддерживают ES-модули, как и Node.js, начиная с 12-й версии.

ES-модули работают нативно в браузере у 93% пользователей. Поэтому их можно и нужно использовать в продакшене. Если вам нужно поддерживать браузеры, которые этого не умеют, например IE11, для них можно делать специальную сборку.
HTM
Следующий инструмент, который может помочь нам, это HTM. Удобная библиотека, с помощью которой можно писать JSX-like-синтаксис с помощью tagged templates, который будет работать прямо в браузере. HTM может работать как с React, так и Preact.
Unpkg
В примерах кода будет использован ресурс unpkg.com. Это просто CDN для npm. Его можно использовать для быстрой проверки гипотез, не скачивая себе на компьютер никаких зависимостей.
Hello, world
Давайте с помощью всего вышеперечисленного напишем простейшее приложение, которое работает прямо в браузере. В примерах будет использован Preact, но это будет работать и с React’ом тоже. Создаем компонент и рендрим его на DOM-ноде:
<!-- index.html --> <body> <div id="app"></div> <script type="module" src="main.js"></script> </body>
// main.js import { h, render } from "https://unpkg.com/preact@10.5.13/dist/preact.module.js"; import htm from "https://unpkg.com/htm@3.0.4/dist/htm.module.js"; const html = htm.bind(h); const App = ({ name }) => { return html`<div>Hello ${name}</div>`; }; function renderApp() { const element = document.getElementById("app"); render(html`<${App} name="world" />`, element); } renderApp();
Для запуска не понадобилось сборщиков и даже локальной установки зависимостей. Приложение похоже на обычное приложение на React, с которым многие знакомы. Но тут есть что улучшить — например, добавить статическую типизацию.
Статическая типизация
Большие веб-приложения уже не пишутся на голом JavaScript. Используя статическую типизацию, например TypeScript или Flow, можно избежать многих ошибок и сделать код более поддерживаемый.
В примерах будем использовать TypeScript — это своеобразная надстройка над JS, которая позволяет писать типы в коде. Чтобы TypeScript работал в браузере, можно использовать специальный синтаксис и писать все типы в JSDocs. Получаем работающую статическую проверку кода, при этом ��од все еще работает прямо в браузере:
// main.js import { html, render, } from "https://unpkg.com/htm@3.0.2/preact/standalone.module.js"; /** @type {import('preact').FunctionalComponent<{name: string}>} */ const App = ({ name }) => { return html`<div>Hello ${name}</div>`; }; function renderApp() { const element = document.getElementById("app"); if (!element) throw new Error("element is not found"); render(html`<${App} name="world" />`, element); } renderApp();
Управление импортами
С выходом Chrome 89 (и в Deno 1.8) мы получили нативное использование Import maps — механизма, который позволяет получить контроль над поведением JS-импортов.
Чем Import maps может быть полезен? К примеру, мы хотим использовать библиотеку «preact-router», которая внутри зависит от библиотеки «preact». Когда JS-интерпретатор дойдет до импорта «preact» в «preact-router», он упадет с ошибкой, так как не знает, что такое «preact» и откуда его брать. С помощью Import maps мы можем указать в html-файле, откуда брать зависимость, и JS-интерпретатор в рантайме успешно отработает.
<!-- index.html --> <script type="importmap"> { "imports": { "preact": "https://unpkg.com/preact@10.5.13/dist/preact.module.js", "htm": "https://unpkg.com/htm@3.0.4/dist/htm.module.js", "htm/preact": "https://unpkg.com/htm@3.0.4/preact/index.module.js", "preact-router": "https://unpkg.com/preact-router@3.2.1/dist/preact-router.es.js" } } </script>
// main.js import { html, render } from "htm/preact"; import { Router, Route } from "preact-router"; /** @type {import('preact').FunctionComponent<{ name: string }>} */ const Page = ({ name }) => { return html`<div>${name} page</div>`; }; function App() { return html` <${Router}> <${Route} default component=${() => html`<${Page} name="Home" />`} /> <${Route} path="/about" component=${() => html`<${Page} name="About" />`} /> </Router> `; } function renderApp() { const element = document.getElementById("app"); if (!element) throw new Error("element is not found"); render(html`<${App} />`, element); }
Подводные камни
У описанного выше подхода ограничение: зависимости должны быть ES-модулями. Но ввиду широкой поддержки ES-модулей, скорей всего в будущем, не собирать библиотеки под ES-модули будет моветоном.
Есть еще проблема: проверка типов не работает в tagged templates. Проблема решаемая, и уже есть инструменты, которое это умеют в lit (аналог htm), например lit-analyzer. Также есть issue в гитхаб в репозитории TypeScript, связанное с этим.
Заключение
В итоге удалось построить приложение, которое работает прямо в браузере. Вы можете самостоятельно посмотреть, как это работает, используя transpilation-free-starter-kit. В шаблоне также предусмотрена сборка для продакшена с поддержкой старых браузеров, установка зависимостей из npm и другое.
Это не серебряная пуля, а один из способов писать веб-приложения, используя последние стандарты. На мой взгляд, такой подход хорошо подойдет как для быстрой проверки гипотез, так и для приложений среднего размера, которые не требуют большого количества зависимостей.
Используйте стандарты, пишите код, который исполняется в браузере, и все будет супер!
