Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(loaders): hover decorator #466

Closed
wants to merge 1 commit into from
Closed

feat(loaders): hover decorator #466

wants to merge 1 commit into from

Conversation

pla1d3
Copy link
Contributor

@pla1d3 pla1d3 commented Jan 6, 2021

Это больше как эксперимент, интересно было сделать свой loader для вебпака
Идея в том чтобы в styl писать как обычно :hover. Поскольку те лоадеры которые юзаются щас для css, игнорят все декораторы и псевдоклассы, я написал хук useHover и внедряю его в код при компиляции
Оно работает, но если менять стили в :hover - авторелоада вебпака не будет, т.к. лоадер для .js файлов, соответсвенно изменения прилетают в бандл когда меняется js
Так же возможно не все кейсы AST дерева я учел, и лучше это делать не как лоадер, а как webpack plugin

@cray0000
Copy link
Member

cray0000 commented Jan 6, 2021

красава! очень хорошая идея для нашего движка метапрограммирования.

Насчет реализации:

У нас уже есть 2 плагина, которые занимаются похожими вещами, и конфликтуют с твоим функционалом, поэтому надо скорее всего подумать как встроить твой функционал в них:

  1. babel-plugin-rn-stylename-to-style

    Этот плагин собирает динамическую функцию _processStyleName со всеми входящими параметрами, которая уже в момент исполнения кода динамически генерит пропсы для JSX элемента -- как style, так и part-запчасти -- titleStyle, iconStyle и любые другие.

    То есть он понимает как именно селекторы, которые были указаны в CSS, надо заматчить с указанным у JSX элемента styleName.

    Посмотри тест кейсы этого плагина -- один набор тестов тестит чисто babel плагин (папка __tests__) и второй набор тестов тестит алгоритм матчинга селекторов (в том числе ::part()) (папка test)

  2. babel-plugin-rn-stylename-inline

    Этот плагин позволяет писать стили инлайново в темплейт-строках styl и css, так же как мы пишем pug. И собственно этот плагин как раз требует того, чтобы производить компиляцию так же как это ты делаешь в своем. Только написана эта компиляция полиморфно -- и для css и для styl файлов, и потенциально потом для sass и т.д..

Общие замечания:

1.

template() бабелевский ты правильно используешь, молодец.

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

Инициализируй шаблон заранее, а потом просто его используй. Как я это вот тут делаю, например.

2.

По return искать функцию руками не надо ближайшую, можно найти ее через метод .getFunctionParent().

Но в твоем случае этот метод не очень подходит, и надо игнорить функции с маленькой буквы, потому что в компоненте может быть что-то типа function renderItem () {}, который рендерит часть шаблона. Вот пример где я так делаю.

3.

Главная архитектурная проблема заключается в том, что мы хотим, чтобы желательно все было реактивным и с хот-релоадингом. И к тому же мы не хотим компилить дважды файл .styl -- один раз лоадером, а второй раз еще внутри самого JS'а. Так что useHover нам скорее всего нужен динамический.

Такой, что если впринципе styleName юзаются в компоненте, то автоматом сразу вверх компонента добавится хук const hover = useHover(...)

И здесь мы приходим к следующим проблемам:

  1. что делать, когда мы захотим чтобы у нескольких JSX элементов были :hover? Нам нужен свой useHover для каждого.
  2. что делать, когда помимо :hover мы еще захотим добавить :active, а потом возможно и другие псевдоклассы или псевдоэлементы.

Я думаю решение здесь это придумать какой-то универсальный хук, который будет всегда подключаться автоматом вверху компонента, и в котором будет храниться состояние для каждого инстанса styleName. Причем даже несколько состояний -- active, hover:

const [__styleStates, __setStyleStates] = __useStyleStates()

И прокидывать эту переменную в функцию _processStyleName (которая babel-plugin-rn-stylename-to-style/matcher.js).

Эта функция:

  1. принимает все стили, которые есть (и те, что в отдельном файле, и те, что инлайново через темплейт строки styl и css написаны)

  2. матчит селекторы. Сейчас она матчит селекторы типа .root, .root.modifier1.modifier2, .root:part(icon). Будет уметь матчить еще и :hover

  3. Если она находит, что у какого-то селектора есть :hover, то она возвращает помимо пропсов style, iconStyle и т.д. еще и пропсы onMouseMove, onMouseLeave, которые будут примерно такими:

    onMouseMove: () => __setStyleStates('.button.dark', { hover: true })
    onMouseLeave: () => __setStyleStates('.button.dark', { hover: false })
    

__setStyleStates типа принимает селектор первым аргументов и по нему включает и выключает флаги. Ну и функция __processStyleName соответственно в процессе матчинга смотрит на все флаги для всех селекторов в __styleStates и решает заматчить его стили или нет.

@cray0000
Copy link
Member

cray0000 commented Jan 6, 2021

Ну и любые бабелевские плагины надо писать по TDD, так что если начнешь по моим замечаниям расширять функционал плагина babel-plugin-rn-stylename-to-style (по-идее весь твой функционал должен быть именно в нем), то начни с модификации/добавления тест кейсов для бабель плагина (__tests__/index.spec.js) и для матчера (test/matcher.mjs).

Оба тестов запускаются просто через yarn test

Чтобы обновить снапшоты jest для тестов бабель плагина, надо запустить через yarn test -u

@cray0000
Copy link
Member

cray0000 commented Jan 6, 2021

а, ну и как ты понял, этот функционал должен быть не вебпак лоадером, а бабелевским плагином. А точнее расширением функционала плагина babel-plugin-rn-stylename-to-style

@pla1d3
Copy link
Contributor Author

pla1d3 commented Jan 6, 2021

Инициализируй шаблон заранее, а потом просто его используй. Как я это вот тут делаю, например.

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

По return искать функцию руками не надо ближайшую, можно найти ее через метод .getFunctionParent().

Об этом методе не знал, по идеи мне нужно найти функцию выше которой по дереву будут уже идти аргументы из body, т.е. функция должна быть аргументом body. Вроде как юзать хуки в компонентах которые лежат в теле компонента не канает, нужно именно в родителя инитить.

что делать, когда мы захотим чтобы у нескольких JSX элементов были :hover? Нам нужен свой useHover для каждого.

Щас я сделал чтобы каждому присваивался свой хук. Типа hoverProps1, hoverProps2 и т.д.
Ну да, нужно не только один ховер обрабатывать, но и остальные псевдоклассы

Ну + - идею доработки я понял, нужно вникнуть в код babel-plugin-rn-stylename-to-style, ну и вообще все что связанно с компиляцией css на startupjs
Из того что я смотрел, там "css-to-react-native-transform" щас тупо игнорит все css символы типа: < : + и т.д., кроме если это не :part или :export вроде, т.е. щас в css объекте стили связанные с ховер теряются.
В общем надо покопаться в этих плагинах будет, пока очень поверхностное понимание, но я в принципе подозревал что еще раз компилить css и js отдельно, это так себе затея
Таску в трелло создать? Или когда будет время пилить?

@cray0000
Copy link
Member

cray0000 commented Jan 7, 2021

@simonprod угумс, css-to-react-native-transform я как раз форкнул, чтобы сделать чтобы он не игнорил :part().

Так что можешь просто в наш форк сделать потом ПР, чтобы оно и :hover не игнорило: https://github.com/startupjs/css-to-react-native-transform

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

Вот еще тебе кстати хороший проект, чтобы посмотреть как через бабель реализовывать всякие штуки для препроцессинга стилей в реакте:
https://github.com/jacobp100/cssta

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

Еще можешь посмотреть как styled-components реализованы, они тоже вроде как бабелевский плагин работают. Но в их исходниках я не разбирался, поэтому какой-то оценки дать не могу.

@cray0000
Copy link
Member

cray0000 commented Jan 7, 2021

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

ага, ща он у тебя для каждого файла темплейт распарсивает, а может даже и чаще.

Об этом методе не знал, по идеи мне нужно найти функцию выше которой по дереву будут уже идти аргументы из body, т.е. функция должна быть аргументом body. Вроде как юзать хуки в компонентах которые лежат в теле компонента не канает, нужно именно в родителя инитить.

ну я там не говорил об обьявлении компонента внутри другого компонента, это какая-то дичь выходит) Я говорил именно о функциях типа renderItem. Например когда надо отрендерить список какой-то, и для этого пишут вспомогательный renderItem, не вынося при этом его в отдельный компонент.

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

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

Щас я сделал чтобы каждому присваивался свой хук. Типа hoverProps1, hoverProps2 и т.д.
Ну да, нужно не только один ховер обрабатывать, но и остальные псевдоклассы

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

Вариант делать useHover1 useHover2 на самом деле тоже хороший, так как именно на этапе компиляции создается только то, что собственно и нужно компоненту.

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

Так что если окажется, что динамический вариант (то есть с поддержкой хот-релоада) сделать будет сложно, то можно оставить твою реализацию текущую. Или еще лучше - сделать 2 опции в бабелевском плагине для компиляции :hover - компиляция в динамику (в режиме разработки), и оптимизация для продакшена - компиляция в статику.

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

Ну + - идею доработки я понял, нужно вникнуть в код babel-plugin-rn-stylename-to-style, ну и вообще все что связанно с компиляцией css на startupjs
Из того что я смотрел, там "css-to-react-native-transform" щас тупо игнорит все css символы типа: < : + и т.д., кроме если это не :part или :export вроде, т.е. щас в css объекте стили связанные с ховер теряются.
В общем надо покопаться в этих плагинах будет, пока очень поверхностное понимание, но я в принципе подозревал что еще раз компилить css и js отдельно, это так себе затея
Таску в трелло создать? Или когда будет время пилить?

на это в предыдущем комменте ответил)

@pla1d3 pla1d3 added the WIP work in progress label Jan 11, 2021
@pla1d3 pla1d3 mentioned this pull request Nov 11, 2021
@pla1d3 pla1d3 closed this Nov 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WIP work in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants