Галоўная / Артыкулы

Чаму Codegen вымагае прыкладчыка спэсіфікацыі першым для React Native TurboModules

Пазіце, як React Native Codegen ператварае спефікацыю TurboModule на TypeScript у контракт часу зборкі, што пагубнае, калі яго працяўначыць, і дзе правілы больш не прыменяюцца.

Выдавец: REACTAPP.TOP8 хв чытання1819 слоў

Крыніцы: Spec First Isn’t a House Rule — the New Architecture Toolchain Enforces It

Багато команд, які падрэбнуюць бібліятэкі React Native, у своіх інструкцыях для участнікаў праставляюць правілу: перш чым змяніць які-небудзь код на натыўным рэверсе, трэба змяніць спефікацыю TurboModule. Гэта выглядае як праганак да певнага стылю, такія правіла, якія людзі маюць спадзявацца, што ўчастнікі будуць сягладваць пад тым прытыскам часу. У новай архітэктуры такога няма — Codegen генеруе мост на натыўны рэверсе на адной засадзе спефікацыі, таму сам процес зборкі вялікая мераюць порядак выконання задач. У гэтым артыкуле пояснюецца, як практыкуецца гэта правілу, што на самай працы ў iOS і Android калі хтось прабуе аб’ект, і якія типы модуляў не падпадаюць пад яго дэйство.

Праганак, які на практыцы стае обмежэнням

Разглянем адміністратара, які рашыцца заўсёды эканоміць час: спачатку ён дадае метод на мове програмавання, а пасля вяртаецца і апдэйтуе спефікацыю. У модуле, які падтрымваецца аб’ектам Codegen, такі план не дае ніякога рэзультата. Не трэба, каб які-небудзь лінтар чы ўпорачык выступіл проты; гэта робяць кампайляр і створаны код. Важлівая частка — гэта усвядомленне, што правілы ніколі не былі проста командным звычкам, якія інструментальная сутка проста тольеруе. Це ўласті інструментальной суткі, якія команда проста запісала.

Разлік не толькі ў формулюваннях. Перакананню можна паставіць прыоритет над канечным тэрміном. Але контракту, які дзейсніцца пад час кампайліравання, гэта зробіць немагчыма, і розумеў, з якім з іх вы маеце справу, зменшвае спосаб планавання змян у бібліятэце.

Файл спефікацыі — цэнтр TurboModule

Кожны новы турбомодуль архітектуры мае адзін файл на TypeScript, як правило src/Native<Name>.ts, у яком выявляюцца кожныя методы, якіе пераходзяць межу між JavaScript і натыўным кодам. Кэпсула для SDK дзяржавых улогінаванняў можа называцца src/NativeNaverLogin.ts; кэпсула для адзінакоў у магазіне праграмаў можа выкарыстоваць src/NativeInAppReview.ts. Элемент codegenConfig у файле package.json пакета паведамляе Codegen, дзе знаходзіцца гэты файл і які тип артэфакта трэба стварыць на яго аднойчыну.

Як правіло, порядак прост: кожны метод, які JavaScript мае можлівасць вызваць, спачатку выявляецца ў спефікацыі, а толькі пасля чаго рэалізуецца у натыўным кодзе. Спачатку спефікацыя, потым натыўны код — ніколи наадварот. Тое, што робіць гэты порядак непрыемлемым, — это механізм, які працоўвае з спефікацыяй.

Як Codegen ператварае спефікацыю на местныя обавязкі

Codegen — это трансформацыя, якая выконваліваецца пад час будавання. Яна аналізуе спефікацыю TypeScript і стварае моставую базу для кожнай платформы:

  • Android: абстрактны клас, напрыклад NativeNaverLoginSpec. Ваш модуль на Kotlin або Java расшырвае гэты створаны клас, а не безпосереднія расшырэнне ReactContextBaseJavaModule, і кожны метод у спефікацыі стае абстрактным методам, які неабходна рэалізаваць.
  • iOS: інструмент JSI для Objective-C++, які включае елемент перакладу NativeNaverLoginSpecJSI і протакол, якому неабходна падтрымліваць ваша рэалізацыя у формате .mm.

Ключовым элементам ёсць напрамак залежнасці. Натыўны код не описвае моста; ён успадковуе мост, які быў створаны на аднойчынку спецыфікацыі. JavaScript, у свою чаргу, прабывае да натыўнага коду толькі через гэты створаны інтерфейс. Гэты просты факт вялікай меры вялічы, што адбываецца калі бокі можу адступіць адзін ад другога.

Натыўны метод, які не адзначаны ў спецыфікацыі

Якщо вы дадаеце метод у натыўны бок без яго адзначэння ў спецыфікацыі, Codegen не стварыў нічога для яго. На Android няма абстрактнага методу, які можна было б перазначыць, і на iOS няма запісу протакола.

На Android найбольш распашчытны спосаб напісання такога методу — з викорыстаннем override, але гэта не дапамагае скомпілявацыі, таму што створаная родныя класа не мае адпаведнага элементу. Нават якщо вы адмаўліце модифікатор, і файл скомпілюецца, метод не ўходзіць у створаны інтерфейс, таму JavaScript не мае да яго шляху.

У iOS такія проблема проявляецца тыхаючы. Метод існуе ў вашам файле .mm і компілюецца без працы, але ён не включаны у протакол, які выкалывач выкарыстоўвае для адкрыцья методаў, таму JavaScript ніколі не можа да яго дасягнуць. Такі «безшумны» нефункцыональны метод, можа, нават горшы за памылку компіляцыі: нічто не паведамляе вас пра яго несправнасць, пакуль хтось не вызове яго з JavaScript і не адкрое, што ён відсутны.

Метод спецыфікацыі без адпаведнай рэалізацыі

Адваротны варіант таксама не працуе. Якщо спецыфікацыя заявляе метод, але натыўны кантэнт яго не рэалізуе, Codegen усё раве стварае для яго мост, і JavaScript-проксі з задоволеннем адкрывае гэты метод.

У Android-сістэме кампайляр Kotlin або Java адхоўляе клас, які расшырвае створаную абстрактную спэсіфікацыю, не реалізаваўшы аднаго з яе абстрактных методаў, і гэта ёсць найкращы варыянт, калі толькі адбуваецца ранняя памылка. У iOS відсутнасць неабходнага метода протаколу зазвычай выклікае толькі паведамленне пра памылку кампайляра; рэальная памылка выявляецца пад час адработкі коду у виглядзе збою «unrecognised-selector», калі JavaScript вызывае гэты метод.

У будзь-якім случае, гэтыя два элементы з’едынаюцца пад час стварэння коду. Вы не можете даць JavaScript новыя можлівасці, вырэшыўшы проблему толькі на аднай з сторон. Вас не стрымвае коментар пра перагляд, а система типаў і створаныя з’еднуваючы элементы, якія не праграмаваны для супрацоўкі.

Чаму гэтае правіла прыменяецца да кожнай бібліятэкі Codegen

Якбы падчыннае ставленне да спефікацый было простаў стылем практыкі, яно бы дзейснілася толькі ў репазітарыях, чыяя адпаведальныя за ўтриманне выбралі яго. Але прымус да ўжывання выходзіць з Codegen, а Codegen належыць React Native. Кожная бібліятэка, якая дазволяе викорыстоўваць Codegen за дапамогою блока codegenConfig у новай архітэктуре, падпадае пад тыя ж правіла, незалежна ад таго, чыі ўяўлялі яе создаталія пра падчыннае ставленне да спефікацый чы ні.

Афіцыйная дакументацыя React Native паследважае гэта ў своім формулюванні. Опісваючы папку з выходным кантэнтам, якую стварае Codegen, ёна праказвае створаны абстрактны клас як што-та, што неабходна реалізаваць у рамках Turbo Native Module. Слова «неабходна» ўжываецца як ключоўка: гэта ўмова, якая пераказвываецца пад час компіляцыі коду, а не рэкамендацыя. У інструкціях для спальноты таксама описваецца тое жа паведанне ззовні: як толькі codegenConfig налаштавана, з’яўляецца створаны файл Native...Spec, які называецца так, як указана codegenConfig у файле package.json. Разныя автары, разныя репазітарыі — аднаковыя правілы.

Існуе таксама корыстная падтверджэння з працоўнага кірунку. Якщо зовсамна не заўважыць поле codegenConfig у файле package.json, то запуск процэсу CocoaPods install на iOS паказвае гэтыя недастаткі:

[Codegen] The "codegenConfig" field is not defined in package.json

Codegen тады проста пераяходзіць да наступнага пакета. Цяплык створаны так, каб зупініцца, а не намагацца знайсці альтернатыўны спосаб. Спефікацыя — гэта не факультатывны документ, які можа стаць застарэлым; гэта вхідны данны, адносна якога організаваны весь процес генеравання.

У сукупнасці падход «спефікацыя перш» ёсць структурным наследкам таго, як цяплык працуе з вхіднымі даннымі. Бібліятэкі следуюць гэтаму падходу, таму што яны адпавядаюць структуре Codegen. Бібліятэка, якая, здаецца, ігноруе гэты падход, або ўсё яшчэ працуе на старой архітектуре, або вовсе не налаштавана codegenConfig.

Дзе не прыменяецца кантракт

Назваць гэта кантрактом інструментальнага комплексу ёсць правільна, але легка перэтварыць гэта тверджэння на занадто шырокае. Межы трэба чытка адзначыць:

  • Модулі моста пакета Legacy. Модулі, створаны на базе моста даўнейшага пакета JSI з RCTBridgeModule у iOS або ReactContextBaseJavaModule у Android, без якіх-леба спецыфікацый, існуюць ранейш за Codegen. У іх няма генераванага адпаведнага коду, таму нічыя механізмы не прымусваюць пэўнага порядку. Толькі дисцыпліна можа тут дапамагчы.
  • Пакеты без codegenConfig. Без гэтага блока не генеруецца жадных рэзультатаў, і ланцоўка ад спецыфікацый да выконання ў натыўнай форме ніколі не пачынаецца.
  • Компаненты для адміністрацыі тканіны. Компаненты, заявленыя з type: "components", таксама праходзяць через Codegen, таму цілком логічна асумпцыя пра адпаведную дисципліну. Аднак вышэўшыя меркаванні базуюцца на Turbo Native Modules (type: "modules"), і створаныя для компанентов файлы разныя, таму трэба ставіцца да справы з компанентамі як да шчыльна верагаемага, а не як да усталенага стандарту, і пераканацца ў гэтым за дапамогою чынной дакументацыі для вашай версіі React Native.
  • Тэза, якая застоіцца моцна, є вузкай: для New Architecture TurboModules, створаных Codegen на адной з спефікацый JavaScript чытаў TypeScript, якая є офіцыйна задокументаваным спосабам іх стварэння, пры практычным выконанні проекта дэтальнае адпрацоўвання спефікацыі є обав’язковым.

    Таксама варта згадаць, што TurboModules на адвалоре Codegen — гэта не ўсё, што можна выкорыстоўваць для стварэння натыўных модуляў сёньня. Альтернатывы, такія як Nitro Modules, выкарыстоўваюць сае власныя механізмы генеравання коду та прыўязкі; стаття на пра тое, як статычныя прыўязкі Nitro парабейнаюць TurboModules, розглядае гэтыя пераклады.

    Процес работы, які фактычна праўяе система

    Калі механізм стае зрозумелым, порядак виконання задач больш не ўтварае пытання для суджэння. Дадаванне методу да TurboModule адбываецца за аднам напрамку:

    1. Зменіце спефікацыю ў src/Native<Name>.ts, каб заявіць новы метод та яго типы.
    2. Запустіце Codegen: pod install генеруе код для iOS, а синхронізацыя чы будова з Gradle — код для Android.
  • Застосавайце няўжоўна створаныя падпісы на кожной платформе. Крокі для Android і iOS ўзаемна незалежныя і можуць выконвацца паралельна, але оба залежыць ад 2-го кроку.
  • Паўтарна экспортуйце метод з публічнага JavaScript API бібліятэкі.
  • З гэтага выленяецца калькі практычных прыемаў:

    • Аналізавайце змяны спэкіфікацыі разам з змянамі API. Спэкіфікацыя ў сутнасці являе сабою публічны натыўны контракт бібліятэкі, таму змяны ў яй трэба аднарадзіць так сама рэтельна, як і змяны ў экспортуемых JavaScript-тыпах.
    • Паўтарна створуйце падпісы пры дэбагаванні. Калі здаецца, што натыўны метод не існуе, частай прычыной є застарэлы код, створаны пасля прахіднага pod install або синхронізацыі Gradle.
    • Запускайце будовы для обох платформ у CI. Паколькі iOS можа выдаць беззвучныя памылкі, тады как Android — гучныя, сама зелёная будова для Android не паводзіць да вынікнення заключэння, што «мост» завершаны.

    Заключэнне

    З’ясаванне таго, хто „володзіць“ правилам прыоритэта спефікацыі, — гэта не педантызм. Калі вы разумеете, што гэта механічны процес, вы перестаеце спрыяваць гэта як прадакт, які трэба взважыць праз часавы термін. Не існуе варыянту „толькі раз“ з падключэнням натыўнага методу спачатку і выправленням спефікацыі пазней, адтуды што такі скорае рашэнне не дае рабочага коду.

    • Codegen стварае натыўны мост з спефікацыі TypeScript, таму натыўны код рэалізуе спефікацыю, а не визначае мост.
    • Абяканне ў будзь-какім напрамку ведае до невыкарыстоўвання: іноды пад час компіляцыі, іноды непазначальна на iOS, іноды як збой пад час выканання.
    • Контракт распрастраняецца на TurboModules, падтрымваныя Codegen; старыя мостовыя модулі і пакеты без codegenConfig застаюцца пахацелыя, а компаненты Fabric патрабуюць свайгое перакранчэння.
  • У вашым адказе працаваць з кодам застаўце пункт «спачатку спэкыфікацыя», але сформулюйце яго як апісанне таго, як працюе процес зборкі, а не як запатэнне пра належную практыку. Процес зборкі будзе яго адмаўляць, незалежна ад таго, што напісана ў дасягледзе.