Открыта продажа билетов на Heart of Clojure, 2го августа в Бельгии https://heartofclojure.eu/
Пару недель назад опубликовал последний скринкаст о создании cljs враппера для React (все видео в плейлисте https://www.youtube.com/playlist?list=PLHOTezm7WWkl4rfd6j0fsL65DrxfWz4XM). В итоге библиотека практически добралась до статуса production-ready, поэтому грех было не довести ее до ума: https://github.com/roman01la/uix
Помимо uix уже существует еще два враппера для React с хуками, саспенсом и т.д. Изначально у каждого был свой интерпретатор Hiccup, но в итоге мы взяли самый быстрый из Reagent и коллективными усилиями выжали из него максимум, даже немного обогнали оригинал. По бенчмаркам интерпретация + построение VDOM вышли ~2.5x медленее чистого React, тогда как Reagent примерно в 3.5x медленее.
Ну и реализация враппера на основе хуков много проще и понятнее, чем поверх классов в JS.
Из интересного конкретно в библиотеке uix:
Есть опциональная прекомпиляция Hiccup макросом. Компилятор сделан на основе форка компилятора Hicada с фолбеком на интерпретацию когда невозможно установить тип значения в compile-time. Type inference в cljs сегодня может давать информацию по типам примитивных значений, что уже сокращает количество вставок интерпретатора в скомпилированный Hiccup (https://kevinlynagh.com/notes/fast-cljs-react-templates/). Чтобы еще больше сократить вставки я экспериментирую с рантайм проверкой через clojure.spec, выходит, что в compile-time функции со спекой пропускают проверку типов откладывая в рантайм проверку спекой.
Есть поддержка ленивой подгрузки компонентов через React.lazy + Suspense, оформлена в виде знакомой кложуристам формы
И есть возможность перехватывать интерпретацию Hiccup. Например чтобы использовать CSS-in-JS в виде инлайновых стилей. тогда можно писать вот так:
Пример всего этого можно посмотреть вот здесь: https://github.com/roman01la/uix/blob/master/dev/uix/example.cljs
Помимо uix уже существует еще два враппера для React с хуками, саспенсом и т.д. Изначально у каждого был свой интерпретатор Hiccup, но в итоге мы взяли самый быстрый из Reagent и коллективными усилиями выжали из него максимум, даже немного обогнали оригинал. По бенчмаркам интерпретация + построение VDOM вышли ~2.5x медленее чистого React, тогда как Reagent примерно в 3.5x медленее.
react x 22989 ops/s, elapsed 435ms
uix-interpret x 9166 ops/s, elapsed 1091ms
reagent-interpret x 6680 ops/s, elapsed 1497ms
Ну и реализация враппера на основе хуков много проще и понятнее, чем поверх классов в JS.
Из интересного конкретно в библиотеке uix:
Есть опциональная прекомпиляция Hiccup макросом. Компилятор сделан на основе форка компилятора Hicada с фолбеком на интерпретацию когда невозможно установить тип значения в compile-time. Type inference в cljs сегодня может давать информацию по типам примитивных значений, что уже сокращает количество вставок интерпретатора в скомпилированный Hiccup (https://kevinlynagh.com/notes/fast-cljs-react-templates/). Чтобы еще больше сократить вставки я экспериментирую с рантайм проверкой через clojure.spec, выходит, что в compile-time функции со спекой пропускают проверку типов откладывая в рантайм проверку спекой.
Есть поддержка ленивой подгрузки компонентов через React.lazy + Suspense, оформлена в виде знакомой кложуристам формы
require.(require-lazy '[uix.components :refer [ui-list]])И есть возможность перехватывать интерпретацию Hiccup. Например чтобы использовать CSS-in-JS в виде инлайновых стилей. тогда можно писать вот так:
[:button {:css {:font-size 14}} "Submit"]Пример всего этого можно посмотреть вот здесь: https://github.com/roman01la/uix/blob/master/dev/uix/example.cljs
Апдейт по UIx. Забыл включить кэш в интерпретаторе, теперь рендер в 2х быстрее реагента и всего в 1.5х медленнее ванильного реакта. Никогда не думал, что можно выжать столько из cljs, при этом сохранив относительную идиоматичность в коде.
Успешно "украл" и адаптировал сериализатор Hiccup из Rum для серверного рендеринга на JVM. Поверх него сделал возможность отдавать разметку кусками пока она рендерится, например пушить в manifold stream и отдавать на клиент через aleph.
Добавил синтаксис в Hiccup для порталов в реакте [:-> element :#selector] и сделал для них рендеринг на сервере, но оказалось что сам реакт не умеет этого делать, поэтому пришлось откатить :)
В целом вышла неплохая библиотека, параллельно обкатываю на пэт проекте.
https://github.com/roman01la/uix
Успешно "украл" и адаптировал сериализатор Hiccup из Rum для серверного рендеринга на JVM. Поверх него сделал возможность отдавать разметку кусками пока она рендерится, например пушить в manifold stream и отдавать на клиент через aleph.
Добавил синтаксис в Hiccup для порталов в реакте [:-> element :#selector] и сделал для них рендеринг на сервере, но оказалось что сам реакт не умеет этого делать, поэтому пришлось откатить :)
В целом вышла неплохая библиотека, параллельно обкатываю на пэт проекте.
https://github.com/roman01la/uix
GitHub
GitHub - roman01la/uix: Idiomatic ClojureScript interface to modern React.js
Idiomatic ClojureScript interface to modern React.js - roman01la/uix
О динамических стилях и прекомпиляции макросом.
Стили можно прекомпилировать макросом для оптимизации в рантайме. Например трансформировать декларацию в виде мапы в литерал JS объекта, чтобы обойтись без этой трансформации в рантайме:
Статические части стилей можно вообще писать в файл и оставлять только динамические. Обычно 90% стилей статичны, поэтому имеет смысл убрать их из райнтайма:
Можно пойти еще дальше и перенести в статику константные значения типа
В таком подходе есть минус — записывая в файл на этапе компиляции нет возможности убрать неиспользуемые стили из рантайма.
Стили можно прекомпилировать макросом для оптимизации в рантайме. Например трансформировать декларацию в виде мапы в литерал JS объекта, чтобы обойтись без этой трансформации в рантайме:
{:top 10} -> {top: 10}.Статические части стилей можно вообще писать в файл и оставлять только динамические. Обычно 90% стилей статичны, поэтому имеет смысл убрать их из райнтайма:
10 :color styles/white}
{:color styles/white}
coponent.css -> .component {top: 10}
Можно пойти еще дальше и перенести в статику константные значения типа
(def ^:const white "#fff") (обязательно нужен хинт ^:const), тогда такие значение можно определить анализатором (cljs.analyzer) и заинлайнить в статическую часть стилей. Это хороший кейс когда есть набор переиспользуемых цветов, размеров и т.д.В таком подходе есть минус — записывая в файл на этапе компиляции нет возможности убрать неиспользуемые стили из рантайма.
Неплохой блог о графике. Для погружения в работу с низкоуровневыми графическими API http://vbomesh.blogspot.com/
Хороший пример использования GraalVM (полиглот VM) для рендернига Reagent приложений на сервере (вызов Node.js из JVM) https://nextjournal.com/kommen/react-server-side-rendering-with-graalvm-for-clojure
Nextjournal
React Server Side Rendering with GraalVM for Clojure
Опубликовали все записи докладов с Curry On! https://www.youtube.com/channel/UC-WICcSW1k3HsScuXxDrp0w
Интересный доклад о файберах в JVM https://youtu.be/r6P0_FDr53Q
Интересный доклад о файберах в JVM https://youtu.be/r6P0_FDr53Q
YouTube
Curry On!
Share your videos with friends, family, and the world
Файберы в кложе 🙌 https://twitter.com/roman01la/status/1153989387211948032?s=09
Ещё перевести на них core.async, чтобы он действительно напоминал гошный CSP.
Ещё перевести на них core.async, чтобы он действительно напоминал гошный CSP.
Twitter
Roman Liutikov
Nice, Fibers and Continuations in Clojure with Project Loom
Небольшой эксперимент с добавлением async/await в ClojureScript https://twitter.com/roman01la/status/1155602189311717377?s=20
В данном случае вместо core.async, который всегда генерирует огромную стейт машину, здесь я добавил в компилятор обработку функций с мета-тегом
Дальше сгенерированный JS код отдается в Closure Compiler который в зависимости от указанной версии JS на выходе может оставить async/await как есть или трансформировать в генераторы или ту же стейт машину.
Это интересный эксперимент, но в реальном мире добавление нового, платформозависимого синтаксиса только больше навредит языку и разработчикам. async/await нету в Clojure JVM, поэтому сходу страдает портируемость кода. Ну и в целом необходимость в async/await на клиенте — большой вопрос. Часто вам приходиться разруливать нетривиальные асинхронные флоу во фронтенде? async/await именно для этого неплохо подходит, а отправить пару запросов достаточно промисов.
В данном случае вместо core.async, который всегда генерирует огромную стейт машину, здесь я добавил в компилятор обработку функций с мета-тегом
^:async, которые компилятором эмитят синтаксис асинхронных функций в JS async function ..., и специальную форму await.Дальше сгенерированный JS код отдается в Closure Compiler который в зависимости от указанной версии JS на выходе может оставить async/await как есть или трансформировать в генераторы или ту же стейт машину.
Это интересный эксперимент, но в реальном мире добавление нового, платформозависимого синтаксиса только больше навредит языку и разработчикам. async/await нету в Clojure JVM, поэтому сходу страдает портируемость кода. Ну и в целом необходимость в async/await на клиенте — большой вопрос. Часто вам приходиться разруливать нетривиальные асинхронные флоу во фронтенде? async/await именно для этого неплохо подходит, а отправить пару запросов достаточно промисов.
Twitter
Roman Liutikov
Alright, hacked the compiler. Here's async functions in cljs that emits JavaScript's async/await syntax.
На заметку: многие Webpack плагины решаются макросом в Clojure. Например require SVG иконок в инлайновые компоненты.
Через 30 минут вечерний стрим https://www.youtube.com/c/roman01la/live будем работать над патчами в cljs compiler
Интересный эксперимент с перфомансом чтения transit (https://github.com/cognitect/transit-cljs) в cljs.
Для ридера можно переопределять функции трансформации в map и vector. По умолчанию transit использует transient версии этих структур во время десериализации transit. Это уже быстрее, чем персистентые структуры, но если заменить дефолтные хендлеры на transient структуры (transient bean и transient arrayvector) из библиотеки для интеропа данных cljs-bean (https://github.com/mfikes/cljs-bean), то скорость чтения выростает на 30%.
Так выходит потому, что структуры данных в cljs-bean основаны на объектах JavaScript, а их transient версии тупо мутируют эти объекты, поэтому оверхед минимальный.
С сериализацией transit сложнее и она очень медленная :(
В целом если вам не нужно гонять в браузер расширенные типы данных, то лучше использовать JSON.
Для ридера можно переопределять функции трансформации в map и vector. По умолчанию transit использует transient версии этих структур во время десериализации transit. Это уже быстрее, чем персистентые структуры, но если заменить дефолтные хендлеры на transient структуры (transient bean и transient arrayvector) из библиотеки для интеропа данных cljs-bean (https://github.com/mfikes/cljs-bean), то скорость чтения выростает на 30%.
Так выходит потому, что структуры данных в cljs-bean основаны на объектах JavaScript, а их transient версии тупо мутируют эти объекты, поэтому оверхед минимальный.
С сериализацией transit сложнее и она очень медленная :(
В целом если вам не нужно гонять в браузер расширенные типы данных, то лучше использовать JSON.
GitHub
GitHub - cognitect/transit-cljs: Transit for ClojureScript
Transit for ClojureScript. Contribute to cognitect/transit-cljs development by creating an account on GitHub.
Недавно отправил патч в cljs compiler который убирает мертвые ветки в if когда тип значения в выражении теста известен заранее (if test then else) https://clojure.atlassian.net/browse/CLJS-2875
Патч нашел пару интересных ошибок в type inference компилятора.
Одна в loop/recur, когда изменяется тип параметра в цикле https://clojure.atlassian.net/browse/CLJS-3158
Вторая в reify, связанная с присвоением некорректного типа https://clojure.atlassian.net/browse/CLJS-3160
Патч нашел пару интересных ошибок в type inference компилятора.
Одна в loop/recur, когда изменяется тип параметра в цикле https://clojure.atlassian.net/browse/CLJS-3158
Вторая в reify, связанная с присвоением некорректного типа https://clojure.atlassian.net/browse/CLJS-3160
Отправил патч в Reagent который ускоряет интерпретацию Hiccup на ~34% https://github.com/reagent-project/reagent/pull/450
Это хороший пример, когда в "горячем коде" стоит использовать нативные методы в обход рефлексии.
Это хороший пример, когда в "горячем коде" стоит использовать нативные методы в обход рефлексии.
GitHub
Optimize dash-to-prop-name & capitalize fns by roman01la · Pull Request #450 · reagent-project/reagent
This PR optimizes string manipulation code in reagent.impl.util/dash-to-prop-name and reagent.impl.util/capitalize which results in ~34% speedup when interpreting Hiccup, according to synthetic ben...
В Reagent есть хитрый макрос with-let, который создаёт локальные биндинги в компоненте, но при этом они создаются только один раз, во время инициализации компонента.
Такую же штуку можно сделать макросом с хуком useRef. Например макрос use-let принимает биндинги и разворачивается в обычный let, но значения биндингов оборачиваются в useRef, таким образом в рефе фиксируется только начальное значение.
https://github.com/roman01la/uix/commit/3fdcc82388294812c56d6b866f4a7db276d9d567
Такую же штуку можно сделать макросом с хуком useRef. Например макрос use-let принимает биндинги и разворачивается в обычный let, но значения биндингов оборачиваются в useRef, таким образом в рефе фиксируется только начальное значение.
https://github.com/roman01la/uix/commit/3fdcc82388294812c56d6b866f4a7db276d9d567
GitHub
add use-let macro · roman01la/uix@3fdcc82
Experimental ClojureScript wrapper for modern React.js - roman01la/uix
Вышел апдейт cljs REPL Replete v2.2, теперь с веб-версией репла. Android версия для x64 получила 64bit сборку V8 https://twitter.com/mfikes/status/1173336849466765312?s=20
Twitter
Mike Fikes
Replete 2.2 has been released for iOS, macOS and Android, featuring the Fira Code font, and the newest member of the family: Replete Web! https://t.co/PNc5ZmEtZf #ClojureScript #Clojure
В ClojureScript есть хитрый метатег
Допустим вот такой пример
В первом случае вызывается функция
В данном случае, с вектором, вызов делегируется на фукцию
Однако такой вызов все равно пройдет через проверку типов, чтобы найти реализацию протокола для конкретного типа данных.
Маркер
Этот маркер часто применяеться в стандартной библиотеке cljs. В будущем проверки с
^not-native, который используется как маркер для генерирования прямого вызова имплементации протокола, в обход рефлексии. Эта оптимизация имеет смысл в «горячем» коде, который часто отрабатывает, например в цикле.Допустим вот такой пример
(def v [1 2 3])
(nth v 0)
(-nth v 0)
(-nth ^not-native v 0)
В первом случае вызывается функция
cljs.core/nth, которая работает не только с IIndexed, но и с JS массивами, строками, и неиндексированными последовательностями.В данном случае, с вектором, вызов делегируется на фукцию
cljs.core/-nth в протоколе IIndexed. То есть зная, что тип данных реализует конкретный протокол можно напрямую вызывать его реализацию (как во втором примере).Однако такой вызов все равно пройдет через проверку типов, чтобы найти реализацию протокола для конкретного типа данных.
Маркер
^not-native заставит компилятор сгенерировать прямой вызов реализации протокола. То есть вместо чего-то такого cljs.core._nth.invoke(v, 0), будет сгенерирован код типа v.cljs.core.IIndexed_nth(0).Этот маркер часто применяеться в стандартной библиотеке cljs. В будущем проверки с
implements? в коде будут автоматически маркировать проверяемые значения.(if (implements? IIndexed v)
(-nth v) ;; неявный ^not-native
(nth v))
Недавно нужно было поднять браузерные тесты на CI. В итоге оказалось проще сделать что-то свое, чем разбираться в настройке существующих решений.
Скрипт в 30 строк запускающий тесты Puppeteer https://github.com/roman01la/uix/blob/master/core/noscripts/test.js и конфиг для Circle CI https://github.com/roman01la/uix/blob/master/.circleci/config.yml
profit
Скрипт в 30 строк запускающий тесты Puppeteer https://github.com/roman01la/uix/blob/master/core/noscripts/test.js и конфиг для Circle CI https://github.com/roman01la/uix/blob/master/.circleci/config.yml
profit
GitHub
uix/core/noscripts/test.js at master · roman01la/uix
Idiomatic ClojureScript interface to modern React.js - roman01la/uix
Мы сегодня вышли в закрытую бету и ребята сделали клёвый лендинг 🙌 https://pitch.com/
Pitch
Presentation software for fast-moving teams | Pitch
Pitch is the complete pitching platform that enables any team to quickly create sleek presentations that get results. Sign up for free.