Obsługa Package Exports w React Native
Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Wraz z wydaniem React Native 0.72, Metro — nasze narzędzie do budowania JavaScript — zawiera teraz wsparcie beta dla pola "exports" w package.json. Po aktywacji dodaje następujące funkcjonalności:
-
Projekty React Native będą działać z większą liczbą pakietów npm "out-of-the-box"
-
Nowe możliwości definiowania API przez pakiety i targetowanie React Native
-
Nieliczne zmiany łamiące kompatybilność w rozpoznawaniu pakietów (w przypadkach brzegowych)
W tym poście omówimy, jak działa Package Exports i co te zmiany oznaczają dla ciebie jako developera aplikacji React Native lub maintainera pakietów.
Czym jest Package Exports?
Wprowadzone w Node.js 12.7.0, Package Exports to nowoczesne podejście dla pakietów npm do określania punktów wejścia — mapowania ścieżek wewnętrznych pakietu, które mogą być importowane z zewnątrz oraz plików, do których powinny się rozwiązywać.
Obsługa "exports" poprawia współpracę projektów React Native z szerszym ekosystemem JavaScript (używane dziś w ~16.6 tys. pakietów) oraz daje autorom pakietów ustandaryzowany zestaw funkcji do tworzenia pakietów wieloplatformowych dla React Native.
"exports" może być używane obok lub zamiast pola "main" w pliku package.json.
{
"name": "@storybook/addon-actions",
"main": "./dist/index.js",
...
"exports": {
".": {
"node": "./dist/index.js",
"import": "./dist/index.mjs",
"default": "./dist/index.js"
},
"./preview": {
"import": "./dist/preview.mjs",
"default": "./dist/preview.js"
},
...
"./package.json": "./package.json"
}
}
Oto przykładowy kod aplikacji korzystający z powyższego pakietu poprzez import różnych ścieżek wewnętrznych @storybook/addon-actions.
import {action} from '@storybook/addon-actions';
// -> '@storybook/addon-actions/dist/index.js'
import {action} from '@storybook/addon-actions/preview';
// -> '@storybook/addon-actions/dist/preview.js'
import helpers from '@storybook/addon-actions/src/preset/addArgsHelpers';
// Inaccessible - not listed in "exports"!
Kluczowe funkcje Package Exports to:
-
Hermetyzacja pakietu: Tylko ścieżki zdefiniowane w
"exports"mogą być importowane z zewnątrz — dając pakietom kontrolę nad ich publicznym API. -
Aliasy ścieżek: Pakiety mogą definiować własne ścieżki mapujące na inne lokalizacje plików (w tym poprzez wzorce ścieżek) — umożliwiając przenoszenie plików przy zachowaniu publicznego API.
-
Eksporty warunkowe: Ścieżka może rozwiązywać się do różnych plików w zależności od środowiska. Na przykład dla środowisk
"node","browser"lub"react-native"— zastępując specyfikację pola"browser".
Pełne możliwości "exports" są szczegółowo opisane w specyfikacji punktów wejścia pakietów Node.js.
Ponieważ te funkcje pokrywają się z istniejącymi koncepcjami React Native (takimi jak rozszerzenia specyficzne dla platformy), a "exports" było już obecne w ekosystemie npm, skonsultowaliśmy się ze społecznością React Native, aby upewnić się, że nasza implementacja spełni potrzeby deweloperów (PR, finalny RFC).
Dla deweloperów aplikacji
Obsługę Package Exports można już włączyć w wersji beta.
-
Importy z pakietów korzystających z funkcji Package Exports (takich jak Firebase czy Storybook) powinny teraz działać zgodnie z założeniami.
-
Projekty React Native for Web używające Metro będą teraz mogły korzystać z eksportu warunkowego
"browser", eliminując potrzebę stosowania obejść.
Włączenie Package Exports wprowadza kilka zmian łamiących kompatybilność w szczególnych przypadkach, które możesz przetestować już teraz.
W przyszłej wersji React Native obsługa Package Exports będzie domyślnie włączona. W sytuacji typu "co było pierwsze, jajko czy kura?" aplikacje React Native były dotąd wyjątkiem dla niektórych pakietów migrujących do "exports" lub korzystały z obejścia poprzez pole "react-native". Obsługa tych funkcji w Metro pozwoli ekosystemowi na dalszy rozwój.
Włączanie Package Exports (beta)
Obsługę Package Exports można włączyć w pliku metro.config.js Twojej aplikacji za pomocą opcji resolver.unstable_enablePackageExports.
const config = {
// ...
resolver: {
unstable_enablePackageExports: true,
},
};
Metro udostępnia dwie dodatkowe opcje resolvera konfigurujące działanie eksportów warunkowych:
-
unstable_conditionNames- Zbiór nazw warunków używanych przy rozwiązywaniu eksportów warunkowych. Domyślnie używane są['require', 'import', 'react-native']. -
unstable_conditionsByPlatform- Dodatkowe nazwy warunków używane przy rozwiązywaniu dla danej platformy docelowej. Domyślnie dla platformy'web'używany jest warunek'browser'.
Pamiętaj o użyciu predefiniowanych ustawień Jest dla React Native! Jest domyślnie obsługuje Package Exports. W testach możesz nadpisać rozwiązywane customExportConditions za pomocą opcji testEnvironmentOptions.
Jeśli używasz TypeScripta, zachowanie resolvera można dopasować, ustawiając moduleResolution: 'bundler' i resolvePackageJsonImports: false w pliku tsconfig.json twojego projektu.
Weryfikacja zmian w Twoim projekcie
W istniejących projektach zalecamy, aby wcześni użytkowncy wykonali te kroki, aby sprawdzić, czy po włączeniu unstable_enablePackageExports wystąpią zmiany w rozwiązywaniu zależności. To jednorazowy proces. Prawdopodobnie nie będzie żadnych zmian, ale chcemy, aby deweloperzy włączali tę funkcję świadomie.
💡 Validating changes in your project
If you are not using Yarn, substitute yarn for npx (or the relevant tool used in your project).
-
Get all resolved dependencies (before changes):
# Replace index.js with your entry file if needed, such as App.js
yarn metro get-dependencies index.js --platform android --output before.txt- Expo CLI: Run
npx expo customize metro.config.jsif your project doesn't have ametro.config.jsfile yet. - For full coverage, substitute
--platform androidfor the other platforms in use by your app (e.g.ios,web).
- Expo CLI: Run
-
Enable
resolver.unstable_enablePackageExportsinmetro.config.js. -
Get all resolved dependencies (after changes):
yarn metro get-dependencies index.js --platform android --output after.txt -
Compare!
diff before.txt after.txt
Zmiany łamiące kompatybilność
Zdecydowaliśmy się na implementację Package Exports w Metro zgodną ze specyfikacją (co wymagało wprowadzenia pewnych zmian łamiących kompatybilność), ale zachowującą wsteczną kompatybilność w innych przypadkach (co pomaga aplikacjom z istniejącymi importami w stopniowej migracji).
Kluczową zmianą łamiącą kompatybilność jest to, że gdy pakiet dostarcza "exports", będzie on sprawdzany jako pierwszy (przed innymi polami package.json) — a dopasowany cel podścieżki zostanie użyty bezpośrednio.
-
Metro nie będzie rozszerzał
sourceExtswzględem specyfikatora importu. -
Metro nie będzie rozwiązywał rozszerzeń specyficznych dla platformy względem pliku docelowego.
Więcej szczegółów znajdziesz w zmianach łamiących kompatybilność w dokumentacji Metro.
Hermetyzacja pakietu jest łagodna
Gdy Metro napotka podścieżkę niewymienioną w "exports", powróci do starszej metody rozdzielczości. Jest to funkcja kompatybilnościowa mająca na celu zmniejszenie problemów użytkowników związanych z wcześniej dozwolonymi importami w istniejących projektach React Native.
Zamiast zgłaszać błąd, Metro zaloguje ostrzeżenie.
warn: You have imported the module "foo/private/fn.js" which is not listed in
the "exports" of "foo". Consider updating your call site or asking the package
maintainer(s) to expose this API.
Planujemy wdrożyć tryb ścisły dla hermetyzacji pakietów w przyszłości, aby dostosować się do domyślnego zachowania Node.js. Dlatego zalecamy, aby wszyscy programiści zajęli się tymi ostrzeżeniami, jeśli zostaną zgłoszone przez użytkowników.
Dla opiekunów pakietów (podgląd)
Zgodnie z naszym planem wdrożenia, obsługa Package Exports będzie domyślnie włączona w większości projektów w następnej wersji React Native (0.73) jeszcze w tym roku.
Nie planujemy w najbliższym czasie usuwania obsługi pola "main" ani innych obecnych funkcji rozdzielczości pakietów.
Package Exports umożliwia ograniczenie dostępu do wewnętrznych elementów pakietu oraz zapewnia bardziej przewidywalne możliwości dla bibliotek, aby celowały w React Native i React Native for Web.
Jeśli już używasz "exports"
Jeśli twój pakiet używa "exports" wraz z obecnym polem głównym "react-native", prosimy o uwzględnienie zmian łamiących kompatybilność dla użytkowników opisanych powyżej. Dla użytkowników włączających tę funkcję w Metro, "exports" będzie teraz brany pod uwagę jako pierwszy podczas rozdzielczości modułów.
W praktyce przewidujemy, że główną zmianą dla użytkowników będzie wymuszenie (poprzez ostrzeżenia) niedostępnych podścieżek w ich aplikacjach, wynikające z respektowania hermetyzacji pakietu "exports".
Migracja do "exports"
Dodanie pola "exports" do twojego pakietu jest całkowicie opcjonalne. Obecne funkcje rozdzielczości pakietów będą zachowywać się identycznie dla pakietów, które nie używają "exports" — i nie planujemy usuwania tego zachowania.
Uważamy, że nowe funkcje "exports" zapewniają atrakcyjny zestaw możliwości dla opiekunów pakietów React Native.
-
Uszczelnij swoje API pakietu: To świetny moment, aby przejrzeć API modułu twojego pakietu, które może być teraz formalnie zdefiniowane za pomocą eksportowanych aliasów podścieżek. Zapobiega to dostępowi użytkowników do wewnętrznych API, zmniejszając powierzchnię dla błędów.
-
Eksporty warunkowe: Jeśli twój pakiet jest przeznaczony dla React Native for Web (tzn.
"react-native"i"browser"), teraz dajemy pakietom kontrolę nad kolejnością rozdzielczości tych warunków (patrz następny nagłówek).
Jeśli zdecydujesz się wprowadzić "exports", zalecamy, aby zrobić to jako zmianę łamiącą kompatybilność. Przygotowaliśmy przewodnik migracji w dokumentacji Metro, który zawiera informacje, jak zastąpić funkcje takie jak rozszerzenia specyficzne dla platformy.
Prosimy nie polegać na łagodnych zachowaniach implementacji Metro. Chociaż Metro jest wstecznie kompatybilne, pakiety powinny postępować zgodnie z tym, jak "exports" jest udokumentowane w specyfikacji i ściśle implementowane przez inne narzędzia.
Nowy warunek "react-native"
Wprowadziliśmy "react-native" jako warunek społecznościowy (do użytku z eksportami warunkowymi). Reprezentuje on React Native, framework, który znajduje się obok innych rozpoznawalnych środowisk wykonawczych, takich jak "node" i "deno" (RFC).
Definicje warunków społecznościowych — "react-native"
Będzie dopasowywane przez framework React Native (wszystkie platformy). Aby wskazać React Native for Web, warunek "browser" powinien być określony przed tym warunkiem.
To zastępuje poprzednie pole główne "react-native". Kolejność priorytetów rozwiązywania była wcześniej określana przez projekty, co powodowało niejednoznaczność przy używaniu React Native for Web. W przypadku "exports" pakiety konkretnie definiują kolejność rozwiązywania warunkowych punktów wejścia – eliminując tę niejednoznaczność.
"exports": {
"browser": "./dist/index-browser.js",
"react-native": "./dist/index-react-native.js",
"default": "./dist/index.js"
}
Zdecydowaliśmy się nie wprowadzać warunków "android" i "ios" ze względu na powszechność innych metod wyboru platform oraz złożoność potencjalnego działania tego mechanizmu między frameworkami. Zamiast tego prosimy używać API Platform.select().
Przyszłość: Stabilne "exports" domyślnie włączone
W kolejnej wersji React Native planujemy usunąć przedrostek unstable_ dla tej funkcji (po rozwiązaniu zaplanowanych optymalizacji wydajnościowych i błędów) i domyślnie włączyć rozwiązywanie Package Exports.
Dzięki włączeniu "exports" dla wszystkich możemy rozwijać społeczność React Native – na przykład podstawowe pakiety React Native mogą zostać zaktualizowane, aby lepiej oddzielać moduły publiczne i wewnętrzne.

Podziękowania
Podziękowania dla członków społeczności React Native za opinie na temat RFC: @SimenB, @tido64, @byCedric, @thymikee.
Ogromne podziękowania dla @motiz88 i @robhogan z Meta za wsparcie rozwoju tej funkcji.
