Nagtapos ang nakaraang post sa pagturo rito: ang shared-singleton contract, at ang pagkakamaling nagpapa-crash sa app sa pag-launch. Saklaw ng post na ito ang dalawa. Kung ano talaga ang ibig sabihin ng shared, ang tatlong option na kumokontrol dito, at ang pagkabigong nararanasan ng isang remote kapag sinira nito ang contract sa isang library na may native side. Maingay, agaran, at pinapangalanan nito ang sarili ang pagkabigong iyon, kaya ito ang isa sa mga pinakamadaling ayusin.
Magpapatuloy tayo mismo kung saan tumigil ang post 2. Kung sumunod ka sa tutorial noon, manatili sa sarili mong code. Kung hindi, magsimula mula sa tapos na estado ng post 2:
git clone https://github.com/warrendeleon/react-native-module-federation
cd react-native-module-federation
git checkout post-02-first-remote
Ano talaga ang ibig sabihin ng “shared” sa post 2
Idineklara ng post 2 ang react at react-native bilang shared singletons at nagpatuloy lang. Narito ang kalahati ng host, mula sa apps/host/rspack.config.mjs:
shared: {
react: { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
'react-native': {
singleton: true,
eager: true,
requiredVersion: pkg.dependencies['react-native'],
},
},
Tatlong option ang gumagawa ng trabaho, at bawat isa ay sumasagot sa magkaibang tanong.
Sinasagot ng singleton: true ang “ilang kopya ang puwedeng mabuhay sa runtime?” Isa. Kapag parehong humingi ng react ang host at ang remote, ibinibigay ng Module Federation ang parehong instance sa halip na hayaang mag-load ang bawat isa ng sarili nito. Ito ang pinakamahalagang option. Iniimbak ng React ang state ng mga hook nito sa mga variable na nasa module level, kaya ang dalawang kopya ng React sa isang app ay nangangahulugang dalawang magkahiwalay na bunton ng state, at anumang hook na tinawag laban sa maling bunton ay magta-throw ng error.
Sinasagot ng eager: true ang “handa na ba ang kopyang ito bago tumakbo ang unang linya ng app?” Sa host, oo. Asynchronous ang normal na entry ng Module Federation: inihahanda nito ang share scope, pagkatapos ay sisimulan ang code mo. Hindi ka binibigyan ng React Native ng ganoong puwang. Synchronous ang entry nito, kaya minamarkahan ng host ang mga shared na kopya nito bilang eager para ma-load ang mga ito sa share scope nang maaga, bago mag-render ng kahit ano ang AppRegistry. Hindi kailangan ng remote ang eager, dahil sa oras na mag-load ito, napuno na ng host ang scope.
Sinasagot ng requiredVersion ang “anong mga bersyon ang itinuturing na pareho?” Pina-pin nito ang katanggap-tanggap na range. Magandang ugali ang sabihin ito nang tahasan, pero ang hindi pagsulat nito ay hindi katumbas ng pagpatay sa pagsusuri. Ang tanging kaso kung saan talagang nawawala ang pagsusuri ay ang pagkabigong pinagtatapusan ng post na ito.
Hanggang dito ay post 2 ito na may dagdag na pangangatwiran. Nagiging mahalaga ang contract sa sandaling umasa ang isang remote sa pangatlong library, hindi lang sa React.
Isang totoong dependency: ang safe area
Deprecated na ang built-in na SafeAreaView ng React Native. Ang minementinang kapalit ay ang react-native-safe-area-context, at kasama ito sa kasalukuyang template ng React Native, kaya nasa dalawang app na ito ng repo. Magandang pagsubok ito ng contract dahil gumagana ito sa pamamagitan ng React context: isang SafeAreaProvider na naka-mount malapit sa root ang sumusukat sa safe area ng device, at babasahin ito ng anumang component sa ibaba gamit ang useSafeAreaInsets.
Sa isang federated app, nasa magkaibang bundle ang provider at ang consumer. Ang host ang may-ari ng shell, kaya ang host ang nagmo-mount ng provider. I-rewrite ang apps/host/App.tsx:
import React, { Suspense } from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
const PokedexScreen = React.lazy(() => import('listApp/PokedexScreen'));
export default function App() {
return (
<SafeAreaProvider>
<Suspense fallback={<ActivityIndicator style={styles.loader} size="large" />}>
<PokedexScreen />
</Suspense>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
loader: { flex: 1 },
});
Hindi na pinapadding ng host ang screen mismo. Ibinibigay nito ang safe-area context at iniaabot ang buong canvas sa remote. Ngayon babasahin ng remote ang inset at pananatilihing malayo sa notch ang sarili nitong title. I-update ang apps/list/src/PokedexScreen.tsx:
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const POKEMON = [
{ id: 1, name: 'Bulbasaur' },
{ id: 4, name: 'Charmander' },
{ id: 7, name: 'Squirtle' },
{ id: 25, name: 'Pikachu' },
{ id: 133, name: 'Eevee' },
];
export default function PokedexScreen() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.screen, { paddingTop: insets.top + 24 }]}>
<Text style={styles.title}>Pokédex</Text>
<Text style={styles.subtitle}>Served by the list remote</Text>
<FlatList
data={POKEMON}
keyExtractor={p => String(p.id)}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.number}>#{String(item.id).padStart(3, '0')}</Text>
<Text style={styles.name}>{item.name}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 24, backgroundColor: '#fff' },
title: { fontSize: 28, fontWeight: '700' },
subtitle: { fontSize: 14, color: '#6b7280', marginBottom: 16 },
row: {
flexDirection: 'row',
paddingVertical: 12,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#e5e7eb',
},
number: { width: 56, color: '#9ca3af', fontVariant: ['tabular-nums'] },
name: { fontSize: 16, fontWeight: '500' },
});
May context handshake na ngayon na tumatawid sa bundle boundary: nasa host ang provider, at nasa remote ang tawag na useSafeAreaInsets. Para magkonekta iyon, kailangan ng dalawang app ang parehong SafeAreaProvider, mula sa parehong kopya ng library. Ang isang React context ay kinikilala sa pamamagitan ng object na lumilikha rito. Dalawang kopya ng library ang gumagawa ng dalawang magkaibang context object, at ang isang consumer na nagbabasa ng kopya B ay hinding-hindi makakakita ng provider na naka-mount mula sa kopya A.
Para diyan ang contract. Idagdag ang library sa shared sa parehong config, bilang singleton. Ang host (apps/host/rspack.config.mjs), eager gaya ng iba nitong shared na kopya:
shared: {
react: { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
'react-native': {
singleton: true,
eager: true,
requiredVersion: pkg.dependencies['react-native'],
},
'react-native-safe-area-context': {
singleton: true,
eager: true,
requiredVersion: pkg.dependencies['react-native-safe-area-context'],
},
},
At ang remote (apps/list/rspack.config.mjs), singleton pero hindi eager:
shared: {
react: { singleton: true, requiredVersion: pkg.dependencies.react },
'react-native': {
singleton: true,
requiredVersion: pkg.dependencies['react-native'],
},
'react-native-safe-area-context': {
singleton: true,
requiredVersion: pkg.dependencies['react-native-safe-area-context'],
},
},
Patakbuhin ang dalawang dev server at ang host (ang three-terminal na rutina mula sa post 2). Nagre-render ang Pokédex na nasa ilalim ng Dynamic Island ang title nito, padded ng inset na binasa ng remote mula sa provider ng host. Isang library, isang provider, isang context object, na shared sa dalawang app na magkahiwalay na binuo at ni-publish.
Ngayon, sirain natin
Magbura ng isang entry. Tanggalin ang react-native-safe-area-context mula sa shared block ng remote, na iniiwan ito sa host. Ito ang makatotohanang bersyon ng pagkakamali: ni-share ito ng may-akda ng host, nakalimutan ng may-akda ng remote. I-restart ang dev server ng remote at i-reload ang host.
Hindi nagre-render ang app ng bahagyang maling screen. Nagka-crash ito sa pag-launch:
Uncaught Error: Tried to register two views with the same name RNCSafeAreaProvider
At narito kung bakit ito maingay sa halip na tahimik. Hindi purong JavaScript ang react-native-safe-area-context. May dala itong native view, ang RNCSafeAreaProvider, na nirerehistro nito sa view registry ng React Native sa pag-startup. Isang beses itong nirerehistro ng kopya ng host. Kapag binitawan ng remote ang share, binu-bundle nito ang sarili nitong kopya, at sinusubukan ng kopyang iyon na irehistro ang parehong native na pangalan sa pangalawang pagkakataon. Iisang registry kada app ang pinapanatili ng React Native at tinatanggihan nito ang duplicate. Nangyayari ang crash bago pa man marating ng kahit isang Pokémon ang screen.
Ito ang pattern ng mga library na ang JavaScript ay nagre-register ng isang bagay sa native layer, tulad ng pagre-register ng safe-area context sa provider view nito: isang navigation library, isang gesture handler, anumang tumatawag sa requireNativeComponent o may hawak na native na pagkakakilanlan sa module scope. I-share ito mula sa iisang lugar at gumagana ito. Hayaang magdala ang dalawang bundle ng tig-isa nilang sarili at pareho nilang irerehistro ang parehong native view, at magbabanggaan sila nang maaga at malinaw. Hindi lahat ng library na may native na bahagi ay ganito nabibigo: kayang tiisin ng manipis na wrapper na tumatawag lang sa isang native module ang pagdodoble, dahil walang pagkakakilanlang nagbabanggaan doon. Ang mga nasisira ay nagre-register ng view o may hawak na state, at pinapangalanan pa nga ng error ang view, kaya nakaturo pabalik sa kulang na share ang ayos.
Ibalik ang entry na iyon sa shared block ng remote, at magbu-build at tatakbo muli ang app.
Ang React mismo ay nabibigo nang kasing-ingay, sa ibang dahilan. Tanggalin ang react mula sa shared ng isang remote at magbu-bundle ang remote ng sarili nitong React. Ang unang hook na patatakbuhin ng remote ay sinusuri laban sa maling kopya, at makukuha mo ang kilalang Invalid hook call na red box. Parehong aral: ang library na may hawak na pagkakakilanlan sa module scope ay hindi nakakaligtas kapag na-load nang dalawang beses. Katangian iyon ng mga library na ganoon, hindi patakarang ipinatutupad ng runtime kahit saan.
Ang pagkabigong talagang nananatiling tahimik
Isang kaso lang ang nararapat sa label na “tahimik”, at iyon ay ang requiredVersion, kahit hindi sa paraang inaasahan mo. Tanggalin ang field at, kadalasan, walang nagbabago: bumabalik ang Module Federation sa bersyong nakatala para sa dependency na iyon sa package.json mismo ng paketeng kumukonsumo nito, kaya may range pa ring tinitingnan. May kulang pang isang sangkap ang tahimik na pagkabigo: walang mabalikang bersyon ang bundler. Nangyayari iyon kapag wala ang shared package sa package.json ng consumer, o kapag ang balangkas ng package ang pumipigil sa bundler na makabasa ng bersyon para rito (isang bitag na tinatapakan nang buo ng post tungkol sa contract package). Kapag walang range mula sa alinmang pinagmulan, walang matitingnan ang Module Federation: nilo-load nito ang kopyang nanalo at tumutuloy. Iyon ang dapat bantayan, dahil malinis itong nagbu-build at nai-ship, at lumilitaw lang kapag napunta ang dalawang team sa magkaibang bersyon ng isang dependency. I-pin ang requiredVersion nang tahasan, tulad ng ginagawa ng mga config sa itaas, at hindi na nakadepende ang pagtingin sa kung ano ang nahulaan ng bundler.
Kaya nakakaginhawa ang panuntunang praktikal. Karamihan sa mga paraan ng pagsira sa shared contract ay nagpapakilala ng sarili sa pag-launch. Makitid ang tahimik na isa at maisasara mo ito sa iisang field.
Ang nabuo mo, at ang susunod
May-ari ang host ng iisang SafeAreaProvider. Binabasa ng remote ang mga inset nito habang tumatawid sa bundle boundary, dahil ang dalawang app ay nire-resolve sa iisang shared na kopya ng library. Nakita mong humawak ang contract, pagkatapos ay nakita mong nag-crash nang makalimutan ng isang remote ang kalahati nito, at alam mo na ngayon na ang crash ang mas maawaing resulta.
Tapusin nang eksakto sa huling estado ng post na ito. Inilalathala ng gabay ang mga file na sumusuporta sa build; nasa tag ang mga manifest, config at maliliit na pagbabago. Para maging byte-for-byte na katulad ng tag ang tree mo, i-copy ang reference tree sa ibabaw ng sa iyo. Ang file na tama mong naisulat ay ino-overwrite ng kaparehong laman, at pinupunan ng pag-copy ang hindi inilathala ng teksto:
npx degit@3.8.0 --force warrendeleon/react-native-module-federation#post-03-shared-singleton /tmp/pokedex-ref-03
cp -R /tmp/pokedex-ref-03/. .
Ang susunod sa serye: titigil ang host bilang iisang screen at magiging tunay na shell, na hawak ang tab bar habang ang bawat tab ay isang remote na nakakarga sa runtime.
Mga Sanggunian
- react-native-safe-area-context: ang minementinang safe area library, at ang native view na
RNCSafeAreaProvidersa crash - Module Federation 2.0: ang
sharedcontract:singleton,eager, atrequiredVersion - react-native-module-federation: ang companion repo, sa tag na
post-03-shared-singleton