diff --git a/apps/bare-expo/App.tsx b/apps/bare-expo/App.tsx index c5afd0f31839bf..ffaa52ade3987a 100644 --- a/apps/bare-expo/App.tsx +++ b/apps/bare-expo/App.tsx @@ -3,6 +3,7 @@ import { AppMetricsRoot } from 'expo-observe'; import * as Splashscreen from 'expo-splash-screen'; import React from 'react'; import * as DevMenu from 'expo-dev-menu'; +import ExpoObserve from 'expo-observe'; import MainNavigator, { optionalRequire } from './MainNavigator'; @@ -65,6 +66,10 @@ function useLoaded() { return isLoaded; } +ExpoObserve.configure({ + dispatchingEnabled: true, +}); + export default function Main() { React.useEffect(() => { try { diff --git a/apps/bare-expo/MainNavigator.tsx b/apps/bare-expo/MainNavigator.tsx index 8a44cafcfaeb0e..b220ac8dac2cd9 100644 --- a/apps/bare-expo/MainNavigator.tsx +++ b/apps/bare-expo/MainNavigator.tsx @@ -151,7 +151,11 @@ export default function MainNavigator() { .catch(console.error) .finally(() => { setIsReady(true); - AppMetrics.markInteractive(); + AppMetrics.markInteractive({ + params: { + theme: themeName, + }, + }); }); }, [isReady]); diff --git a/apps/bare-expo/app.json b/apps/bare-expo/app.json index dce0188f9fd8ff..ba91dc25448fc4 100644 --- a/apps/bare-expo/app.json +++ b/apps/bare-expo/app.json @@ -42,9 +42,6 @@ "extra": { "eas": { "projectId": "2c28de10-a2cd-11e6-b8ce-59d1587e6774", - "observe": { - "enableInDebug": true - } } }, "runtimeVersion": "1.0.0", diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index cb2f82445e3e75..64bf1ce0f13767 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -4012,7 +4012,7 @@ SPEC CHECKSUMS: EXUpdates: a0f980531cbcf45906b2489febd4e11a5895f332 EXUpdatesInterface: 5ab8c3e8018ef533a132b9327af5b2a1926dd299 FBLazyVector: 26fd21c75314e101f280d401e97f27d54f3f7064 - hermes-engine: e1ae00897676f600a9439d408b42f8c8ba5dbc24 + hermes-engine: 725fd85144e1348879039099a6be950c471a4f2c libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 @@ -4030,7 +4030,7 @@ SPEC CHECKSUMS: React: 13cf8451582adb1bb324306e1893b91d1cba28c6 React-callinvoker: 91e6a605826b684ad2e623811253b4d0c4196bef React-Core: 46818de5f211b2a2759ac823b591af8a0a95c2c1 - React-Core-prebuilt: 1e81ce373ae6d21c61a961e1be02e77633de3007 + React-Core-prebuilt: 4016009b4cc1d669b1a2369a5d707cdb39fa12ef React-CoreModules: a6a37afee48d4a31ab398640b0795462647d5c67 React-cxxreact: 2ec3e2f7a8ae9303460d4ba94cde183ea90d64cd React-debug: 0d21117b897ce0359c9d2c9dfe952f237476a14a @@ -4100,7 +4100,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 22e2265d86a4e871e5e858f4e7ef1c8d01103680 ReactCodegen: f564776e1b15423920d439de1965d2000433dbd2 ReactCommon: a804bb8d1dcf3ecdec3a77eb8bba19b7863bbbdb - ReactNativeDependencies: ceedcc3a6ff166bea3c3816a22f58c478ffad807 + ReactNativeDependencies: a24dd0e4b4318c05556b4b7bb144738f83775e22 RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283 RNCMaskedView: eb2b2e538afa907f05a5848a1a1ac26092e6fec9 RNCPicker: d74667bdfc08ed389a2a277d95b8faf2349290a9 diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index f6970da55fd9bc..d63ad5d5fa07c9 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -4636,7 +4636,7 @@ SPEC CHECKSUMS: ExpoMailComposer: edd4f46a26ea55ff0e3a83ef0192e79f647911c8 ExpoMediaLibrary: 2fbddcb06042f43076cf5e495e8237ec2ab95726 ExpoMeshGradient: 93cf09380e6d86cd7a525da26dfddab2620a8421 - ExpoModulesCore: 936a4fb73792d8c8bc861d57522539c35fc36e40 + ExpoModulesCore: 13bc5b9a2fefacfab477b20600dc57821aa4a200 ExpoModulesJSI: 793687b04cbaa73ee8548bfdbcfba85954d1b2b5 ExpoModulesTestCore: 62ce59e8c8162b449e65467e0421240256ba6732 ExpoModulesWorklets: 3a4d6451e29822c01c397da92be1f962a0f870fe @@ -4661,7 +4661,7 @@ SPEC CHECKSUMS: ExpoVideoThumbnails: 2340f0b7f599c9ce6ba49a885f783de919cf4dd3 ExpoWebBrowser: b65b3921741b51c5513e2a369f59c37076987d9b EXStructuredHeaders: e25ac67c966d3795153dfdb40bfd3b999df18929 - EXUpdates: 33cea909186babc6befefeed3596a82b6c1d63de + EXUpdates: 56ce06b32bea90efb1e4c6b28d73e4cfb472b71b EXUpdatesInterface: 5ab8c3e8018ef533a132b9327af5b2a1926dd299 fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: 4cd65993d9ef677523093deeb7d8710f39fb9ed7 @@ -4674,7 +4674,7 @@ SPEC CHECKSUMS: FirebaseRemoteConfigInterop: 85bdce8babed7814816496bb6f082bc05b0a45e1 FirebaseSessions: f5c6bfeb66a7202deaf33352017bb6365e395820 fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac - glog: e56ede4028c4b7418e6b1195a36b1656bb35e225 + glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 @@ -4690,7 +4690,7 @@ SPEC CHECKSUMS: PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 Quick: 83e25bf349dd84f894b024f48033274512d6129b - RCT-Folly: 36c4f904fb6cd0219dcb76b94e9502d2a72fab0b + RCT-Folly: 121436bcc4611f6bde5c09bf35f0a7a82cef1969 RCTDeprecation: 5c945c659e2d68c1428f4a732c83198a4ad6a659 RCTRequired: c98eb09689f6a2a2c75f6fd419fa94fe71a672c9 RCTSwiftUI: 88767b796e06cd4af8dca2f7a3f97fccf1d723e0 diff --git a/apps/native-component-list/src/screens/UI/PullToRefreshBoxScreen.android.tsx b/apps/native-component-list/src/screens/UI/PullToRefreshBoxScreen.android.tsx index db4767980ec2aa..a85ff5da111631 100644 --- a/apps/native-component-list/src/screens/UI/PullToRefreshBoxScreen.android.tsx +++ b/apps/native-component-list/src/screens/UI/PullToRefreshBoxScreen.android.tsx @@ -1,4 +1,4 @@ -import { PullToRefreshBox, Host, LazyColumn, ListItem } from '@expo/ui/jetpack-compose'; +import { PullToRefreshBox, Host, LazyColumn, ListItem, Text } from '@expo/ui/jetpack-compose'; import { fillMaxSize } from '@expo/ui/jetpack-compose/modifiers'; import * as React from 'react'; @@ -17,26 +17,36 @@ export default function PullToRefreshBoxScreen() { const basic = useSimulatedRefresh(); return ( - + - Item 1 + + Item 1 + - Item 2 + + Item 2 + - Item 3 + + Item 3 + - Item 4 + + Item 4 + - Item 5 + + Item 5 + diff --git a/docs/components/DocumentationPage.tsx b/docs/components/DocumentationPage.tsx index 6e4b878be8618b..269276e84fa90a 100644 --- a/docs/components/DocumentationPage.tsx +++ b/docs/components/DocumentationPage.tsx @@ -375,7 +375,7 @@ export default function DocumentationPage({

For the complete documentation index, see llms.txt. Use this - Use this file to discover all available pages. + file to discover all available pages.

{children} diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/carousel.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/carousel.mdx index de71ff981fa931..2bce5fcdf02eb9 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/carousel.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/carousel.mdx @@ -11,6 +11,8 @@ import { APIInstallSection } from '~/components/plugins/InstallSection'; Expo UI provides three carousel components matching the official Jetpack Compose [Carousel](https://developer.android.com/develop/ui/compose/components/carousel) API: `HorizontalCenteredHeroCarousel`, `HorizontalMultiBrowseCarousel`, and `HorizontalUncontainedCarousel`. +> **Note:** Carousel is a horizontally scrollable component, so the parent `Host` must provide a finite width on the scroll axis. Use `matchContents={{ vertical: true }}` together with `style={{ width: '100%' }}` (or any finite width). See [Match contents in Host reference](host/#match-contents) for details. + ## Installation @@ -29,7 +31,7 @@ export default function CenteredHeroExample() { const colors = ['#6200EE', '#03DAC5', '#FF5722', '#4CAF50', '#2196F3']; return ( - + {colors.map((color, index) => ( + + **Note:** The date variants render Material's calendar grid and input field, both of which scroll horizontally internally. The parent `Host` must provide a finite width on the horizontal axis, use `matchContents={{ vertical: true }}` together with `style={{ width: '100%' }}` (or any finite width). See [Match contents in Host reference](host/#match-contents) for details. + ## Installation @@ -27,7 +29,7 @@ export default function DatePickerExample() { const [selectedDate, setSelectedDate] = useState(new Date()); return ( - + { setSelectedDate(date); @@ -51,7 +53,7 @@ export default function TimePickerExample() { const [selectedDate, setSelectedDate] = useState(new Date()); return ( - + { setSelectedDate(date); @@ -77,7 +79,7 @@ export default function InputVariantExample() { const [selectedDate, setSelectedDate] = useState(new Date()); return ( - + { setSelectedDate(date); diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/host.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/host.mdx index eed0d9a2dcbd58..349797f8295763 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/host.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/host.mdx @@ -33,6 +33,44 @@ export default function MatchContents() { } ``` +> **Note:** Do not use `matchContents` on the same axis as a scrollable child (`LazyRow`, `LazyColumn`, `Carousel`, or anything using `Modifier.horizontalScroll`/`verticalScroll`). Scrollables require a finite max constraint on their scroll axis and `matchContents` propagates an unbounded one. + +The following example crashes: + +```tsx MatchContentsCrash.tsx +import { Host, LazyRow, Text } from '@expo/ui/jetpack-compose'; + +export default function MatchContentsCrash() { + return ( + + + {Array.from({ length: 5 }).map((_, i) => ( + Item {i} + ))} + + + ); +} +``` + +Either drop `matchContents` on the scroll axis or give the `Host` a finite size on that axis via `style`: + +```tsx MatchContentsFix.tsx +import { Host, LazyRow, Text } from '@expo/ui/jetpack-compose'; + +export default function MatchContentsFix() { + return ( + + + {Array.from({ length: 5 }).map((_, i) => ( + Item {i} + ))} + + + ); +} +``` + ### With style Apply standard React Native styles to the `Host` wrapper. diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/lazycolumn.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/lazycolumn.mdx index 6c5fc6dbf80782..8f931091fe83ce 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/lazycolumn.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/lazycolumn.mdx @@ -42,20 +42,26 @@ export default function BasicLazyColumn() { Use the `verticalArrangement` prop to control how items are spaced within the list. Pass a string value like `'spaceBetween'` or an object like `{ spacedBy: 8 }` for fixed spacing in dp. ```tsx LazyColumnArrangement.tsx -import { Host, LazyColumn, ListItem } from '@expo/ui/jetpack-compose'; +import { Host, LazyColumn, ListItem, Text } from '@expo/ui/jetpack-compose'; export default function LazyColumnArrangement() { return ( - Spaced item 1 + + Spaced Item 1 + - Spaced item 2 + + Spaced Item 2 + - Spaced item 3 + + Spaced Item 3 + diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/host.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/host.mdx index 8cc2d4ef9406c6..db9e8a034513d3 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/host.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/host.mdx @@ -42,6 +42,26 @@ export default function MatchContentsExample() { } ``` +> **Note:** Do not use `matchContents` on the same axis as a scroll container (`ScrollView`, `List`, `Form`, `LazyHStack`, `LazyVStack`). `matchContents` resolves to SwiftUI's `.fixedSize`, which sizes the scroll container to its content. It also leaves nothing past the viewport to scroll into, so scrolling silently stops working. Use `matchContents={{ vertical: true }}` together with `style={{ width: '100%' }}` (or any finite width on the scroll axis). + +```tsx ScrollViewMatchContents.tsx +import { Host, HStack, ScrollView, Text } from '@expo/ui/swift-ui'; + +export default function ScrollViewMatchContents() { + return ( + + + + {Array.from({ length: 20 }).map((_, i) => ( + Item {i} + ))} + + + + ); +} +``` + ### Explicit sizing with style Use `style` to set explicit sizes on the `Host`, such as filling the available space with `flex: 1`. diff --git a/docs/public/static/data/unversioned/expo-document-picker.json b/docs/public/static/data/unversioned/expo-document-picker.json index 4367699c768955..c7bb2e2aca93e3 100644 --- a/docs/public/static/data/unversioned/expo-document-picker.json +++ b/docs/public/static/data/unversioned/expo-document-picker.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-document-picker","variant":"project","kind":1,"children":[{"name":"DocumentPickerAsset","variant":"declaration","kind":2097152,"children":[{"name":"base64","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Base64 string of the file."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"file","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"code","text":"`File`"},{"kind":"text","text":" object for the parity with web File API."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.dom.d.ts","qualifiedName":"File"},"name":"File","package":"typescript"}},{"name":"lastModified","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of last document modification. [Web API specs](https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified)\nThe lastModified provides the last modified date of the file as the number\nof milliseconds since the Unix epoch (January 1, 1970 at midnight). Files\nwithout a known last modified date return the current date."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"mimeType","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Document MIME type."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"name","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Document original name."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"size","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Document size in bytes."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"uri","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"An URI to the local document file."}]},"type":{"type":"intrinsic","name":"string"}}]},{"name":"DocumentPickerCanceledResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing canceled pick result."}]},"children":[{"name":"assets","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`null`"},{"kind":"text","text":" when the request was canceled."}]},"type":{"type":"literal","value":null}},{"name":"canceled","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`true`"},{"kind":"text","text":" when the request was canceled."}]},"type":{"type":"literal","value":true}},{"name":"output","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`null`"},{"kind":"text","text":" when the request was canceled."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"literal","value":null}}]},{"name":"DocumentPickerOptions","variant":"declaration","kind":2097152,"children":[{"name":"base64","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If "},{"kind":"code","text":"`true`"},{"kind":"text","text":", asset url is base64 from the file\nIf "},{"kind":"code","text":"`false`"},{"kind":"text","text":", asset url is the file url parameter"}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]},{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"copyToCacheDirectory","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If "},{"kind":"code","text":"`true`"},{"kind":"text","text":", the picked file is copied to ["},{"kind":"code","text":"`FileSystem.CacheDirectory`"},{"kind":"text","text":"]("},{"kind":"relative-link","text":"./filesystem#filesystemcachedirectory"},{"kind":"text","text":"),\nwhich allows other Expo APIs to read the file immediately. This may impact performance for\nlarge files, so you should consider setting this to "},{"kind":"code","text":"`false`"},{"kind":"text","text":" if you expect users to pick\nparticularly large files and your app does not need immediate read access."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"multiple","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Allows multiple files to be selected from the system UI."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"type","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\nto be picked. It also supports wildcards like "},{"kind":"code","text":"`'image/*'`"},{"kind":"text","text":" to choose any image. To allow any type\nof document you can use "},{"kind":"code","text":"`'*/*'`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'*/*'"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"array","elementType":{"type":"intrinsic","name":"string"}}]}}]},{"name":"DocumentPickerResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing successful and canceled document pick result."}]},"type":{"type":"union","types":[{"type":"reference","name":"DocumentPickerSuccessResult","package":"expo-document-picker"},{"type":"reference","name":"DocumentPickerCanceledResult","package":"expo-document-picker"}]}},{"name":"DocumentPickerSuccessResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing successful pick result."}]},"children":[{"name":"assets","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"An array of picked assets."}]},"type":{"type":"array","elementType":{"type":"reference","name":"DocumentPickerAsset","package":"expo-document-picker"}}},{"name":"canceled","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"If asset data have been returned this should always be "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"literal","value":false}},{"name":"output","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"code","text":"`FileList`"},{"kind":"text","text":" object for the parity with web File API."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.dom.d.ts","qualifiedName":"FileList"},"name":"FileList","package":"typescript"}}]},{"name":"getDocumentAsync","variant":"declaration","kind":64,"signatures":[{"name":"getDocumentAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory]("},{"kind":"relative-link","text":"filesystem/#filesystemcachedirectory"},{"kind":"text","text":").\n> **Notes for Web:** The system UI can only be shown after user activation (e.g. a "},{"kind":"code","text":"`Button`"},{"kind":"text","text":" press).\n> Therefore, calling "},{"kind":"code","text":"`getDocumentAsync`"},{"kind":"text","text":" in "},{"kind":"code","text":"`componentDidMount`"},{"kind":"text","text":", for example, will **not** work as\n> intended. The "},{"kind":"code","text":"`cancel`"},{"kind":"text","text":" event will not be returned in the browser due to platform restrictions and\n> inconsistencies across browsers."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"On success returns a promise that fulfils with ["},{"kind":"code","text":"`DocumentPickerResult`"},{"kind":"text","text":"](#documentpickerresult) object.\n\nIf the user cancelled the document picking, the promise resolves to "},{"kind":"code","text":"`{ type: 'cancel' }`"},{"kind":"text","text":"."}]}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"DocumentPickerOptions","package":"expo-document-picker"},"defaultValue":"{}"}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"DocumentPickerResult","package":"expo-document-picker"}],"name":"Promise","package":"typescript"}}]}],"packageName":"expo-document-picker"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-document-picker","variant":"project","kind":1,"children":[{"name":"DocumentPickerAsset","variant":"declaration","kind":2097152,"children":[{"name":"base64","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Base64 string of the file."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"file","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"code","text":"`File`"},{"kind":"text","text":" object for the parity with web File API."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.dom.d.ts","qualifiedName":"File"},"name":"File","package":"typescript"}},{"name":"lastModified","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of last document modification. [Web API specs](https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified)\nThe lastModified provides the last modified date of the file as the number\nof milliseconds since the Unix epoch (January 1, 1970 at midnight). Files\nwithout a known last modified date return the current date."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"mimeType","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Document MIME type."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"name","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Document original name."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"size","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Document size in bytes."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"uri","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"A URI to the local document file."}]},"type":{"type":"intrinsic","name":"string"}}]},{"name":"DocumentPickerCanceledResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing canceled pick result."}]},"children":[{"name":"assets","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`null`"},{"kind":"text","text":" when the request was canceled."}]},"type":{"type":"literal","value":null}},{"name":"canceled","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`true`"},{"kind":"text","text":" when the request was canceled."}]},"type":{"type":"literal","value":true}},{"name":"output","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Always "},{"kind":"code","text":"`null`"},{"kind":"text","text":" when the request was canceled."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"literal","value":null}}]},{"name":"DocumentPickerOptions","variant":"declaration","kind":2097152,"children":[{"name":"base64","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If "},{"kind":"code","text":"`true`"},{"kind":"text","text":", asset url is base64 from the file\nIf "},{"kind":"code","text":"`false`"},{"kind":"text","text":", asset url is the file url parameter"}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]},{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"copyToCacheDirectory","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If "},{"kind":"code","text":"`true`"},{"kind":"text","text":", the picked file is copied to ["},{"kind":"code","text":"`FileSystem.CacheDirectory`"},{"kind":"text","text":"]("},{"kind":"relative-link","text":"./filesystem#filesystemcachedirectory"},{"kind":"text","text":"),\nwhich allows other Expo APIs to read the file immediately. This may impact performance for\nlarge files, so you should consider setting this to "},{"kind":"code","text":"`false`"},{"kind":"text","text":" if you expect users to pick\nparticularly large files and your app does not need immediate read access."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"multiple","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Allows multiple files to be selected from the system UI."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"type","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\nto be picked. It also supports wildcards like "},{"kind":"code","text":"`'image/*'`"},{"kind":"text","text":" to choose any image. To allow any type\nof document you can use "},{"kind":"code","text":"`'*/*'`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'*/*'"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"array","elementType":{"type":"intrinsic","name":"string"}}]}}]},{"name":"DocumentPickerResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing successful and canceled document pick result."}]},"type":{"type":"union","types":[{"type":"reference","name":"DocumentPickerSuccessResult","package":"expo-document-picker"},{"type":"reference","name":"DocumentPickerCanceledResult","package":"expo-document-picker"}]}},{"name":"DocumentPickerSuccessResult","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Type representing successful pick result."}]},"children":[{"name":"assets","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"An array of picked assets."}]},"type":{"type":"array","elementType":{"type":"reference","name":"DocumentPickerAsset","package":"expo-document-picker"}}},{"name":"canceled","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"If asset data have been returned this should always be "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"literal","value":false}},{"name":"output","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"code","text":"`FileList`"},{"kind":"text","text":" object for the parity with web File API."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.dom.d.ts","qualifiedName":"FileList"},"name":"FileList","package":"typescript"}}]},{"name":"getDocumentAsync","variant":"declaration","kind":64,"signatures":[{"name":"getDocumentAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory]("},{"kind":"relative-link","text":"filesystem/#filesystemcachedirectory"},{"kind":"text","text":").\n> **Notes for Web:** The system UI can only be shown after user activation (e.g. a "},{"kind":"code","text":"`Button`"},{"kind":"text","text":" press).\n> Therefore, calling "},{"kind":"code","text":"`getDocumentAsync`"},{"kind":"text","text":" in "},{"kind":"code","text":"`componentDidMount`"},{"kind":"text","text":", for example, will **not** work as\n> intended. The "},{"kind":"code","text":"`cancel`"},{"kind":"text","text":" event will not be returned in the browser due to platform restrictions and\n> inconsistencies across browsers."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"On success returns a promise that fulfils with ["},{"kind":"code","text":"`DocumentPickerResult`"},{"kind":"text","text":"](#documentpickerresult) object.\n\nIf the user cancelled the document picking, the promise resolves to "},{"kind":"code","text":"`{ type: 'cancel' }`"},{"kind":"text","text":"."}]}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"DocumentPickerOptions","package":"expo-document-picker"},"defaultValue":"{}"}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"DocumentPickerResult","package":"expo-document-picker"}],"name":"Promise","package":"typescript"}}]}],"packageName":"expo-document-picker"} \ No newline at end of file diff --git a/docs/scripts/generate-llms/llms-txt.js b/docs/scripts/generate-llms/llms-txt.js index b3862ab40cfdbd..1895e2546825b4 100644 --- a/docs/scripts/generate-llms/llms-txt.js +++ b/docs/scripts/generate-llms/llms-txt.js @@ -1,18 +1,16 @@ import frontmatter from 'front-matter'; import fs from 'node:fs'; import path from 'node:path'; -import ts from 'typescript'; import { home, learn, general, eas, reference } from '../../constants/navigation.js'; import { generateCrossLinksSection, toBlockquote } from './shared.js'; +import { buildTalksSections } from './transforms/talks-section.js'; const OUTPUT_DIRECTORY_NAME = 'public'; const OUTPUT_FILENAME_LLMS_TXT = 'llms.txt'; const TITLE = 'Expo Documentation'; const DESCRIPTION = 'Expo is an open-source React Native framework for apps that run natively on Android, iOS, and the web. Expo brings together the best of mobile and the web and enables many important features for building and scaling an app such as live updates, instantly sharing your app, and web support. The company behind Expo also offers Expo Application Services (EAS), which are deeply integrated cloud services for Expo and React Native apps.'; -const TALKS_TS_PATH = path.join(process.cwd(), 'public/static/talks.ts'); -const TALKS_JS_PATH = path.join(process.cwd(), 'scripts/generate-llms/talks.js'); function generateItemMarkdown(item) { return `- [${item.title}](${item.url})${item.description ? `: ${item.description}` : ''}\n`; @@ -171,84 +169,14 @@ function processSection(node) { return section; } -function generateVideoUrl(videoId) { - return `https://www.youtube.com/watch?v=${videoId}`; -} - -function processTalks(talks, type = 'video') { - return talks.map(talk => { - if (type === 'podcast' && talk.link) { - return { - title: talk.title, - url: talk.link, - }; - } - - return { - title: talk.title, - url: talk.videoId ? generateVideoUrl(talk.videoId) : '', - }; - }); -} - -async function exportTalksData() { - const { TALKS, PODCASTS, LIVE_STREAMS, YOUTUBE_VIDEOS } = await import('./talks.js'); - return { - title: 'Additional Resources', - description: 'Collection of talks, podcasts, and live streams from the Expo team', - sections: [ - { - title: 'Conference Talks', - items: processTalks(TALKS), - groups: [], - sections: [], - }, - { - title: 'Podcasts', - items: processTalks(PODCASTS, 'podcast'), - groups: [], - sections: [], - }, - { - title: 'Live Streams', - items: processTalks(LIVE_STREAMS), - groups: [], - sections: [], - }, - { - title: 'YouTube Tutorials', - items: processTalks(YOUTUBE_VIDEOS), - groups: [], - sections: [], - }, - ], - }; -} - -function compileTalksFile() { - const inputFileContent = fs.readFileSync(TALKS_TS_PATH, 'utf8'); - const outputFileContent = ts.transpileModule(inputFileContent, { - compilerOptions: { - target: ts.ScriptTarget.ESNext, - module: ts.ModuleKind.ESNext, - moduleResolution: ts.ModuleResolutionKind.Node10, - }, - }).outputText; - - fs.writeFileSync(TALKS_JS_PATH, outputFileContent, 'utf8'); - console.log(` \x1b[1m\x1b[32m✓\x1b[0m Successfully compiled talks.ts to talks.js`); -} - export async function generateLlmsTxt() { try { - compileTalksFile(); - const docSections = Object.values({ home, general, learn, eas, reference: reference.latest }) .flat() .map(processSection) .filter(Boolean); - const talksData = await exportTalksData(); - const allSections = [...docSections, ...talksData.sections]; + const talksSections = await buildTalksSections(); + const allSections = [...docSections, ...talksSections]; await fs.promises.writeFile( path.join(process.cwd(), OUTPUT_DIRECTORY_NAME, OUTPUT_FILENAME_LLMS_TXT), diff --git a/docs/scripts/generate-llms/transforms/talks-section.js b/docs/scripts/generate-llms/transforms/talks-section.js new file mode 100644 index 00000000000000..e42feb0b3fabec --- /dev/null +++ b/docs/scripts/generate-llms/transforms/talks-section.js @@ -0,0 +1,82 @@ +/** + * Adds an "Additional Resources" section to llms.txt with content that doesn't + * live in the docs nav: conference talks, podcasts, live streams, and YouTube + * tutorials. The source of truth is `public/static/talks.ts` (consumed by the + * /additional-resources page). We transpile it to `talks.js` at generation + * time so this script can `import` it without a TypeScript runtime. + * + * Isolated from llms-txt.js so the talks list can grow (more types, different + * URL shapes) without touching the orchestrator script. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import ts from 'typescript'; + +const TALKS_TS_PATH = path.join(process.cwd(), 'public/static/talks.ts'); +const TALKS_JS_PATH = path.join(process.cwd(), 'scripts/generate-llms/talks.js'); + +function generateVideoUrl(videoId) { + return `https://www.youtube.com/watch?v=${videoId}`; +} + +function processTalks(talks, type = 'video') { + return talks.map(talk => { + if (type === 'podcast' && talk.link) { + return { + title: talk.title, + url: talk.link, + }; + } + + return { + title: talk.title, + url: talk.videoId ? generateVideoUrl(talk.videoId) : '', + }; + }); +} + +function compileTalksFile() { + const inputFileContent = fs.readFileSync(TALKS_TS_PATH, 'utf8'); + const outputFileContent = ts.transpileModule(inputFileContent, { + compilerOptions: { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Node10, + }, + }).outputText; + + fs.writeFileSync(TALKS_JS_PATH, outputFileContent, 'utf8'); + console.log(` \x1b[1m\x1b[32m✓\x1b[0m Successfully compiled talks.ts to talks.js`); +} + +export async function buildTalksSections() { + compileTalksFile(); + const { TALKS, PODCASTS, LIVE_STREAMS, YOUTUBE_VIDEOS } = await import('../talks.js'); + return [ + { + title: 'Conference Talks', + items: processTalks(TALKS), + groups: [], + sections: [], + }, + { + title: 'Podcasts', + items: processTalks(PODCASTS, 'podcast'), + groups: [], + sections: [], + }, + { + title: 'Live Streams', + items: processTalks(LIVE_STREAMS), + groups: [], + sections: [], + }, + { + title: 'YouTube Tutorials', + items: processTalks(YOUTUBE_VIDEOS), + groups: [], + sections: [], + }, + ]; +} diff --git a/docs/ui/components/SDKTables/sdk-versions.json b/docs/ui/components/SDKTables/sdk-versions.json index cbe0931c3357bf..12354157c405aa 100644 --- a/docs/ui/components/SDKTables/sdk-versions.json +++ b/docs/ui/components/SDKTables/sdk-versions.json @@ -1,5 +1,19 @@ { "sdkVersions": [ + { + "sdk": "56.0.0", + "android": "7+", + "compileSdkVersion": "36", + "targetSdkVersion": "36", + "buildToolsVersion": "36.0.0", + "ios": "16.4+", + "xcode": "26.2+", + "react-native": "0.85", + "react-native-web": "0.21.0", + "react-native-tvos": "0.85-stable", + "react": "19.2.3", + "node": "22.13.x" + }, { "sdk": "55.0.0", "android": "7+", diff --git a/packages/@expo/inline-modules/build/xcodeProjectUpdates.js b/packages/@expo/inline-modules/build/xcodeProjectUpdates.js index d2912517bd5f51..4710747bf1fa8c 100644 --- a/packages/@expo/inline-modules/build/xcodeProjectUpdates.js +++ b/packages/@expo/inline-modules/build/xcodeProjectUpdates.js @@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateXcodeProject = updateXcodeProject; -const config_plugins_1 = require("expo/config-plugins"); +const config_plugins_1 = require("@expo/config-plugins"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); /** diff --git a/packages/@expo/inline-modules/build/xcodeProjectUpdates.js.map b/packages/@expo/inline-modules/build/xcodeProjectUpdates.js.map index 2fa6df28138a79..6d52ede3908160 100644 --- a/packages/@expo/inline-modules/build/xcodeProjectUpdates.js.map +++ b/packages/@expo/inline-modules/build/xcodeProjectUpdates.js.map @@ -1 +1 @@ -{"version":3,"file":"xcodeProjectUpdates.js","sourceRoot":"","sources":["../src/xcodeProjectUpdates.ts"],"names":[],"mappings":";;;;;AAWA,gDAgEC;AA3ED,wDAAgD;AAChD,4CAAoB;AACpB,gDAAwB;AAMxB;;GAEG;AACI,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,wBAAkD;IAElD,MAAM,uBAAuB,GAAG,wBAAwB,CAAC,kBAAkB,CAAC;IAC5E,+CAA+C;IAC/C,IAAI,uBAAuB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,0BAAS,CAAC,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC;IAC1E,MAAM,UAAU,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;IAChD,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAEtC,MAAM,wBAAwB,GAAgB,IAAI,GAAG,EAAU,CAAC;IAChE,IAAI,OAAO,CAAC,kCAAkC,EAAE,CAAC;QAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,kCAAkC,CAAC,EAAE,CAAC;YAC1E,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7B,SAAS;YACX,CAAC;YACD,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,kCAAkC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,kCAAkC,GAAG,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC1C,MAAM,gBAAgB,GAAG,cAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;QAClE,IAAI,wBAAwB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACnD,SAAS;QACX,CAAC;QAED,iBAAiB,GAAG,IAAI,CAAC;QAEzB,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;QAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC5C,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,GAAG;SACb,CAAC,CAAC;QAEH,OAAO,CAAC,kCAAkC,CAAC,OAAO,CAAC,GAAG;YACpD,GAAG,EAAE,oCAAoC;YACzC,iBAAiB,EAAE,EAAE;YACrB,eAAe,EAAE,EAAE;YACnB,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,gBAAgB;YACtB,UAAU,EAAE,aAAa;SAC1B,CAAC;QAEF,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACpE,IAAI,CAAC,iBAAiB,CAAC,4BAA4B,EAAE,CAAC;gBACpD,iBAAiB,CAAC,4BAA4B,GAAG,EAAE,CAAC;YACtD,CAAC;YACD,iBAAiB,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,YAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"} \ No newline at end of file +{"version":3,"file":"xcodeProjectUpdates.js","sourceRoot":"","sources":["../src/xcodeProjectUpdates.ts"],"names":[],"mappings":";;;;;AAWA,gDAgEC;AA3ED,yDAAiD;AACjD,4CAAoB;AACpB,gDAAwB;AAMxB;;GAEG;AACI,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,wBAAkD;IAElD,MAAM,uBAAuB,GAAG,wBAAwB,CAAC,kBAAkB,CAAC;IAC5E,+CAA+C;IAC/C,IAAI,uBAAuB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,0BAAS,CAAC,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC;IAC1E,MAAM,UAAU,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;IAChD,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAEtC,MAAM,wBAAwB,GAAgB,IAAI,GAAG,EAAU,CAAC;IAChE,IAAI,OAAO,CAAC,kCAAkC,EAAE,CAAC;QAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,kCAAkC,CAAC,EAAE,CAAC;YAC1E,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7B,SAAS;YACX,CAAC;YACD,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,kCAAkC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,kCAAkC,GAAG,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC1C,MAAM,gBAAgB,GAAG,cAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;QAClE,IAAI,wBAAwB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACnD,SAAS;QACX,CAAC;QAED,iBAAiB,GAAG,IAAI,CAAC;QAEzB,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;QAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC5C,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,GAAG;SACb,CAAC,CAAC;QAEH,OAAO,CAAC,kCAAkC,CAAC,OAAO,CAAC,GAAG;YACpD,GAAG,EAAE,oCAAoC;YACzC,iBAAiB,EAAE,EAAE;YACrB,eAAe,EAAE,EAAE;YACnB,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,gBAAgB;YACtB,UAAU,EAAE,aAAa;SAC1B,CAAC;QAEF,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACpE,IAAI,CAAC,iBAAiB,CAAC,4BAA4B,EAAE,CAAC;gBACpD,iBAAiB,CAAC,4BAA4B,GAAG,EAAE,CAAC;YACtD,CAAC;YACD,iBAAiB,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,YAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/packages/@expo/inline-modules/jest.config.ts b/packages/@expo/inline-modules/jest.config.js similarity index 100% rename from packages/@expo/inline-modules/jest.config.ts rename to packages/@expo/inline-modules/jest.config.js diff --git a/packages/@expo/inline-modules/package.json b/packages/@expo/inline-modules/package.json index bd40dc238d87ae..04f1ae3e692f56 100644 --- a/packages/@expo/inline-modules/package.json +++ b/packages/@expo/inline-modules/package.json @@ -29,12 +29,12 @@ "bugs": { "url": "https://github.com/expo/expo/issues" }, + "dependencies": { + "@expo/config-plugins": "workspace:~55.0.6" + }, "devDependencies": { "@types/jest": "^29.2.1", "@types/node": "^22.14.0", "expo-module-scripts": "workspace:*" - }, - "peerDependencies": { - "expo": "workspace:*" } } diff --git a/packages/@expo/inline-modules/src/__tests__/xcodeUpdates.test.ts b/packages/@expo/inline-modules/src/__tests__/xcodeUpdates.test.ts index e7943158d00bd9..0ebccc5bee54dc 100644 --- a/packages/@expo/inline-modules/src/__tests__/xcodeUpdates.test.ts +++ b/packages/@expo/inline-modules/src/__tests__/xcodeUpdates.test.ts @@ -1,4 +1,4 @@ -import { IOSConfig } from 'expo/config-plugins'; +import { IOSConfig } from '@expo/config-plugins'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; diff --git a/packages/@expo/inline-modules/src/xcodeProjectUpdates.ts b/packages/@expo/inline-modules/src/xcodeProjectUpdates.ts index b4749fa10a76dc..f794697cf51ddb 100644 --- a/packages/@expo/inline-modules/src/xcodeProjectUpdates.ts +++ b/packages/@expo/inline-modules/src/xcodeProjectUpdates.ts @@ -1,4 +1,4 @@ -import { IOSConfig } from 'expo/config-plugins'; +import { IOSConfig } from '@expo/config-plugins'; import fs from 'fs'; import path from 'path'; diff --git a/packages/@expo/inline-modules/tests/xcodeUpdates.test.ts b/packages/@expo/inline-modules/tests/xcodeUpdates.test.ts deleted file mode 100644 index e421f5f15848bf..00000000000000 --- a/packages/@expo/inline-modules/tests/xcodeUpdates.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { IOSConfig } from '@expo/config-plugins'; -import { it } from '@jest/globals'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import { updateXcodeProject } from '../src/xcodeProjectUpdates'; - -describe('updateXcodeProject', () => { - let tempProjectRoot: string | null = null; - const FIXTURE_PATH = path.resolve(__dirname, 'bare-project'); - - beforeEach(async () => { - const tempDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'xcode-updates-test-')); - await fs.promises.cp(FIXTURE_PATH, tempDir, { recursive: true }); - tempProjectRoot = tempDir; - }); - - afterEach(async () => { - if (tempProjectRoot && fs.existsSync(tempProjectRoot)) { - await fs.promises.rm(tempProjectRoot, { recursive: true, force: true }); - } - jest.resetAllMocks(); - }); - - it('adds watched directories to the PBX project', async () => { - expect(tempProjectRoot).toBeTruthy(); - expect(tempProjectRoot).toBeDefined(); - tempProjectRoot = tempProjectRoot as string; - - await updateXcodeProject(tempProjectRoot, { watchedDirectories: ['app'] }); - - const pbxProject = IOSConfig.XcodeUtils.getPbxproj(tempProjectRoot); - const objects = pbxProject.hash.project.objects; - const rootGroups = objects.PBXFileSystemSynchronizedRootGroup; - expect(rootGroups).toBeDefined(); - - const rootGroupKeys = Object.keys(rootGroups).filter((key) => !key.endsWith('_comment')); - expect(rootGroupKeys).toHaveLength(1); - - const rootGroupUUID = rootGroupKeys[0]; - expect(rootGroups[rootGroupUUID]).toEqual( - expect.objectContaining({ - isa: 'PBXFileSystemSynchronizedRootGroup', - name: 'app', - }) - ); - }); - - it('does nothing if watchedDirectories is empty', async () => { - expect(tempProjectRoot).toBeTruthy(); - expect(tempProjectRoot).toBeDefined(); - tempProjectRoot = tempProjectRoot as string; - - const pbxProjPath = path.join( - tempProjectRoot, - 'ios', - 'bare-project.xcodeproj', - 'project.pbxproj' - ); - const contentBefore = await fs.promises.readFile(pbxProjPath, 'utf8'); - expect(contentBefore).toBeTruthy(); - - await updateXcodeProject(tempProjectRoot, { watchedDirectories: [] }); - - const contentAfter = await fs.promises.readFile(pbxProjPath, 'utf8'); - - // We shouldn't change the pbxproj if there are no watchedDirectories - expect(contentAfter).toEqual(contentBefore); - }); -}); diff --git a/packages/@expo/prebuild-config/CHANGELOG.md b/packages/@expo/prebuild-config/CHANGELOG.md index 62b73913e296eb..c442bb9690a45f 100644 --- a/packages/@expo/prebuild-config/CHANGELOG.md +++ b/packages/@expo/prebuild-config/CHANGELOG.md @@ -17,6 +17,7 @@ - Make splash screen `backgroundColor` optional, defaulting to `#ffffff`. ([#44098](https://github.com/expo/expo/pull/44098) by [@zoontek](https://github.com/zoontek)) - Removed unused `withAndroidSplashLegacyMainActivity` file. ([#43516](https://github.com/expo/expo/pull/43516) by [@zoontek](https://github.com/zoontek)) - Removed deprecated plugins. ([#43918](https://github.com/expo/expo/pull/43918) by [@kudo](https://github.com/kudo)) +- [Internal] Drop peer dependency looping back to `expo` ([#45125](https://github.com/expo/expo/pull/45125) by [@kitten](https://github.com/kitten)) ## 55.0.7 — 2026-02-25 diff --git a/packages/@expo/prebuild-config/build/getAutolinkedPackages.js b/packages/@expo/prebuild-config/build/getAutolinkedPackages.js index 8ddb81fab8c32f..f40f05961b0db7 100644 --- a/packages/@expo/prebuild-config/build/getAutolinkedPackages.js +++ b/packages/@expo/prebuild-config/build/getAutolinkedPackages.js @@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", { exports.getAutolinkedPackagesAsync = getAutolinkedPackagesAsync; exports.resolvePackagesList = resolvePackagesList; exports.shouldSkipAutoPlugin = shouldSkipAutoPlugin; -function _unstableAutolinkingExports() { - const data = require("expo/internal/unstable-autolinking-exports"); - _unstableAutolinkingExports = function () { +function _exports() { + const data = require("expo-modules-autolinking/exports"); + _exports = function () { return data; }; return data; @@ -21,11 +21,11 @@ function _unstableAutolinkingExports() { * @returns list of packages ex: `['expo-camera', 'react-native-screens']` */ async function getAutolinkedPackagesAsync(projectRoot, platforms = ['ios', 'android']) { - const linker = (0, _unstableAutolinkingExports().makeCachedDependenciesLinker)({ + const linker = (0, _exports().makeCachedDependenciesLinker)({ projectRoot }); const dependenciesPerPlatform = await Promise.all(platforms.map(platform => { - return (0, _unstableAutolinkingExports().scanExpoModuleResolutionsForPlatform)(linker, platform); + return (0, _exports().scanExpoModuleResolutionsForPlatform)(linker, platform); })); return resolvePackagesList(dependenciesPerPlatform); } diff --git a/packages/@expo/prebuild-config/build/getAutolinkedPackages.js.map b/packages/@expo/prebuild-config/build/getAutolinkedPackages.js.map index c34a2b9bac4ab3..b5905ccb7598d6 100644 --- a/packages/@expo/prebuild-config/build/getAutolinkedPackages.js.map +++ b/packages/@expo/prebuild-config/build/getAutolinkedPackages.js.map @@ -1 +1 @@ -{"version":3,"file":"getAutolinkedPackages.js","names":["_unstableAutolinkingExports","data","require","getAutolinkedPackagesAsync","projectRoot","platforms","linker","makeCachedDependenciesLinker","dependenciesPerPlatform","Promise","all","map","platform","scanExpoModuleResolutionsForPlatform","resolvePackagesList","platformPaths","allPlatformPaths","paths","Object","keys","flat","uniquePaths","Set","sort","shouldSkipAutoPlugin","config","plugin","Array","isArray","_internal","autolinkedModules","pluginId","isIncluded","includes"],"sources":["../src/getAutolinkedPackages.ts"],"sourcesContent":["import type { ModPlatform, StaticPlugin } from '@expo/config-plugins';\nimport type { ExpoConfig } from '@expo/config-types';\nimport {\n makeCachedDependenciesLinker,\n scanExpoModuleResolutionsForPlatform,\n} from 'expo/internal/unstable-autolinking-exports';\n\n/**\n * Returns a list of packages that are autolinked to a project.\n *\n * @param projectRoot\n * @param platforms platforms to check for\n * @returns list of packages ex: `['expo-camera', 'react-native-screens']`\n */\nexport async function getAutolinkedPackagesAsync(\n projectRoot: string,\n platforms: ModPlatform[] = ['ios', 'android']\n) {\n const linker = makeCachedDependenciesLinker({ projectRoot });\n const dependenciesPerPlatform = await Promise.all(\n platforms.map((platform) => {\n return scanExpoModuleResolutionsForPlatform(linker, platform);\n })\n );\n return resolvePackagesList(dependenciesPerPlatform);\n}\n\nexport function resolvePackagesList(platformPaths: Record[]) {\n const allPlatformPaths = platformPaths.map((paths) => Object.keys(paths)).flat();\n\n const uniquePaths = [...new Set(allPlatformPaths)];\n\n return uniquePaths.sort();\n}\n\nexport function shouldSkipAutoPlugin(\n config: Pick,\n plugin: StaticPlugin | string\n) {\n // Hack workaround because expo-dev-client doesn't use expo modules.\n if (plugin === 'expo-dev-client') {\n return false;\n }\n\n // Only perform the check if `autolinkedModules` is defined, otherwise we assume\n // this is a legacy runner which doesn't support autolinking.\n if (Array.isArray(config._internal?.autolinkedModules)) {\n // Resolve the pluginId as a string.\n const pluginId = Array.isArray(plugin) ? plugin[0] : plugin;\n if (typeof pluginId === 'string') {\n // Determine if the autolinked modules list includes our moduleId\n const isIncluded = config._internal!.autolinkedModules.includes(pluginId);\n if (!isIncluded) {\n // If it doesn't then we know that any potential plugin shouldn't be applied automatically.\n return true;\n }\n }\n }\n return false;\n}\n"],"mappings":";;;;;;;;AAEA,SAAAA,4BAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,2BAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeE,0BAA0BA,CAC9CC,WAAmB,EACnBC,SAAwB,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,EAC7C;EACA,MAAMC,MAAM,GAAG,IAAAC,0DAA4B,EAAC;IAAEH;EAAY,CAAC,CAAC;EAC5D,MAAMI,uBAAuB,GAAG,MAAMC,OAAO,CAACC,GAAG,CAC/CL,SAAS,CAACM,GAAG,CAAEC,QAAQ,IAAK;IAC1B,OAAO,IAAAC,kEAAoC,EAACP,MAAM,EAAEM,QAAQ,CAAC;EAC/D,CAAC,CACH,CAAC;EACD,OAAOE,mBAAmB,CAACN,uBAAuB,CAAC;AACrD;AAEO,SAASM,mBAAmBA,CAACC,aAAoC,EAAE;EACxE,MAAMC,gBAAgB,GAAGD,aAAa,CAACJ,GAAG,CAAEM,KAAK,IAAKC,MAAM,CAACC,IAAI,CAACF,KAAK,CAAC,CAAC,CAACG,IAAI,CAAC,CAAC;EAEhF,MAAMC,WAAW,GAAG,CAAC,GAAG,IAAIC,GAAG,CAACN,gBAAgB,CAAC,CAAC;EAElD,OAAOK,WAAW,CAACE,IAAI,CAAC,CAAC;AAC3B;AAEO,SAASC,oBAAoBA,CAClCC,MAAqC,EACrCC,MAA6B,EAC7B;EACA;EACA,IAAIA,MAAM,KAAK,iBAAiB,EAAE;IAChC,OAAO,KAAK;EACd;;EAEA;EACA;EACA,IAAIC,KAAK,CAACC,OAAO,CAACH,MAAM,CAACI,SAAS,EAAEC,iBAAiB,CAAC,EAAE;IACtD;IACA,MAAMC,QAAQ,GAAGJ,KAAK,CAACC,OAAO,CAACF,MAAM,CAAC,GAAGA,MAAM,CAAC,CAAC,CAAC,GAAGA,MAAM;IAC3D,IAAI,OAAOK,QAAQ,KAAK,QAAQ,EAAE;MAChC;MACA,MAAMC,UAAU,GAAGP,MAAM,CAACI,SAAS,CAAEC,iBAAiB,CAACG,QAAQ,CAACF,QAAQ,CAAC;MACzE,IAAI,CAACC,UAAU,EAAE;QACf;QACA,OAAO,IAAI;MACb;IACF;EACF;EACA,OAAO,KAAK;AACd","ignoreList":[]} \ No newline at end of file +{"version":3,"file":"getAutolinkedPackages.js","names":["_exports","data","require","getAutolinkedPackagesAsync","projectRoot","platforms","linker","makeCachedDependenciesLinker","dependenciesPerPlatform","Promise","all","map","platform","scanExpoModuleResolutionsForPlatform","resolvePackagesList","platformPaths","allPlatformPaths","paths","Object","keys","flat","uniquePaths","Set","sort","shouldSkipAutoPlugin","config","plugin","Array","isArray","_internal","autolinkedModules","pluginId","isIncluded","includes"],"sources":["../src/getAutolinkedPackages.ts"],"sourcesContent":["import type { ModPlatform, StaticPlugin } from '@expo/config-plugins';\nimport type { ExpoConfig } from '@expo/config-types';\nimport {\n makeCachedDependenciesLinker,\n scanExpoModuleResolutionsForPlatform,\n} from 'expo-modules-autolinking/exports';\n\n/**\n * Returns a list of packages that are autolinked to a project.\n *\n * @param projectRoot\n * @param platforms platforms to check for\n * @returns list of packages ex: `['expo-camera', 'react-native-screens']`\n */\nexport async function getAutolinkedPackagesAsync(\n projectRoot: string,\n platforms: ModPlatform[] = ['ios', 'android']\n) {\n const linker = makeCachedDependenciesLinker({ projectRoot });\n const dependenciesPerPlatform = await Promise.all(\n platforms.map((platform) => {\n return scanExpoModuleResolutionsForPlatform(linker, platform);\n })\n );\n return resolvePackagesList(dependenciesPerPlatform);\n}\n\nexport function resolvePackagesList(platformPaths: Record[]) {\n const allPlatformPaths = platformPaths.map((paths) => Object.keys(paths)).flat();\n\n const uniquePaths = [...new Set(allPlatformPaths)];\n\n return uniquePaths.sort();\n}\n\nexport function shouldSkipAutoPlugin(\n config: Pick,\n plugin: StaticPlugin | string\n) {\n // Hack workaround because expo-dev-client doesn't use expo modules.\n if (plugin === 'expo-dev-client') {\n return false;\n }\n\n // Only perform the check if `autolinkedModules` is defined, otherwise we assume\n // this is a legacy runner which doesn't support autolinking.\n if (Array.isArray(config._internal?.autolinkedModules)) {\n // Resolve the pluginId as a string.\n const pluginId = Array.isArray(plugin) ? plugin[0] : plugin;\n if (typeof pluginId === 'string') {\n // Determine if the autolinked modules list includes our moduleId\n const isIncluded = config._internal!.autolinkedModules.includes(pluginId);\n if (!isIncluded) {\n // If it doesn't then we know that any potential plugin shouldn't be applied automatically.\n return true;\n }\n }\n }\n return false;\n}\n"],"mappings":";;;;;;;;AAEA,SAAAA,SAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,QAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeE,0BAA0BA,CAC9CC,WAAmB,EACnBC,SAAwB,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,EAC7C;EACA,MAAMC,MAAM,GAAG,IAAAC,uCAA4B,EAAC;IAAEH;EAAY,CAAC,CAAC;EAC5D,MAAMI,uBAAuB,GAAG,MAAMC,OAAO,CAACC,GAAG,CAC/CL,SAAS,CAACM,GAAG,CAAEC,QAAQ,IAAK;IAC1B,OAAO,IAAAC,+CAAoC,EAACP,MAAM,EAAEM,QAAQ,CAAC;EAC/D,CAAC,CACH,CAAC;EACD,OAAOE,mBAAmB,CAACN,uBAAuB,CAAC;AACrD;AAEO,SAASM,mBAAmBA,CAACC,aAAoC,EAAE;EACxE,MAAMC,gBAAgB,GAAGD,aAAa,CAACJ,GAAG,CAAEM,KAAK,IAAKC,MAAM,CAACC,IAAI,CAACF,KAAK,CAAC,CAAC,CAACG,IAAI,CAAC,CAAC;EAEhF,MAAMC,WAAW,GAAG,CAAC,GAAG,IAAIC,GAAG,CAACN,gBAAgB,CAAC,CAAC;EAElD,OAAOK,WAAW,CAACE,IAAI,CAAC,CAAC;AAC3B;AAEO,SAASC,oBAAoBA,CAClCC,MAAqC,EACrCC,MAA6B,EAC7B;EACA;EACA,IAAIA,MAAM,KAAK,iBAAiB,EAAE;IAChC,OAAO,KAAK;EACd;;EAEA;EACA;EACA,IAAIC,KAAK,CAACC,OAAO,CAACH,MAAM,CAACI,SAAS,EAAEC,iBAAiB,CAAC,EAAE;IACtD;IACA,MAAMC,QAAQ,GAAGJ,KAAK,CAACC,OAAO,CAACF,MAAM,CAAC,GAAGA,MAAM,CAAC,CAAC,CAAC,GAAGA,MAAM;IAC3D,IAAI,OAAOK,QAAQ,KAAK,QAAQ,EAAE;MAChC;MACA,MAAMC,UAAU,GAAGP,MAAM,CAACI,SAAS,CAAEC,iBAAiB,CAACG,QAAQ,CAACF,QAAQ,CAAC;MACzE,IAAI,CAACC,UAAU,EAAE;QACf;QACA,OAAO,IAAI;MACb;IACF;EACF;EACA,OAAO,KAAK;AACd","ignoreList":[]} \ No newline at end of file diff --git a/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js b/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js index e590f027d2d960..789d2254516672 100644 --- a/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js +++ b/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js @@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", { }); exports.withInlineModules = exports.default = void 0; function _configPlugins() { - const data = require("expo/config-plugins"); + const data = require("@expo/config-plugins"); _configPlugins = function () { return data; }; diff --git a/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js.map b/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js.map index 42511d1bb151fd..7e99b9d53322fa 100644 --- a/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js.map +++ b/packages/@expo/prebuild-config/build/plugins/unversioned/expo-inline-modules/withInlineModules.js.map @@ -1 +1 @@ -{"version":3,"file":"withInlineModules.js","names":["_configPlugins","data","require","createBuildGradlePropsConfigPlugin","AndroidConfig","BuildProperties","createBuildPodfilePropsConfigPlugin","IOSConfig","withInlineModules","config","props","propName","propValueGetter","conf","experiments","inlineModules","JSON","stringify","watchedDirectories","exports","_default","default"],"sources":["../../../../src/plugins/unversioned/expo-inline-modules/withInlineModules.ts"],"sourcesContent":["import type { ExpoConfig } from '@expo/config-types';\nimport { AndroidConfig, IOSConfig } from 'expo/config-plugins';\nconst { createBuildGradlePropsConfigPlugin } = AndroidConfig.BuildProperties;\nconst { createBuildPodfilePropsConfigPlugin } = IOSConfig.BuildProperties;\n\nexport const withInlineModules = (config: ExpoConfig, props: any) => {\n config = createBuildGradlePropsConfigPlugin(\n [\n {\n propName: 'expo.inlineModules.watchedDirectories',\n propValueGetter: (conf) => {\n if (!conf.experiments?.inlineModules) {\n return JSON.stringify([]);\n }\n return JSON.stringify(conf.experiments?.inlineModules?.watchedDirectories ?? []);\n },\n },\n ],\n 'withAndroidInlineModules'\n )(config);\n\n config = createBuildPodfilePropsConfigPlugin(\n [\n {\n propName: 'expo.inlineModules.watchedDirectories',\n propValueGetter: (conf) => {\n if (!conf.experiments?.inlineModules) {\n return JSON.stringify([]);\n }\n return JSON.stringify(conf.experiments?.inlineModules?.watchedDirectories ?? []);\n },\n },\n ],\n 'withIosInlineModules'\n )(config);\n\n return config;\n};\n\nexport default withInlineModules;\n"],"mappings":";;;;;;AACA,SAAAA,eAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,cAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,MAAM;EAAEE;AAAmC,CAAC,GAAGC,8BAAa,CAACC,eAAe;AAC5E,MAAM;EAAEC;AAAoC,CAAC,GAAGC,0BAAS,CAACF,eAAe;AAElE,MAAMG,iBAAiB,GAAGA,CAACC,MAAkB,EAAEC,KAAU,KAAK;EACnED,MAAM,GAAGN,kCAAkC,CACzC,CACE;IACEQ,QAAQ,EAAE,uCAAuC;IACjDC,eAAe,EAAGC,IAAI,IAAK;MACzB,IAAI,CAACA,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAE;QACpC,OAAOC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC;MAC3B;MACA,OAAOD,IAAI,CAACC,SAAS,CAACJ,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAEG,kBAAkB,IAAI,EAAE,CAAC;IAClF;EACF,CAAC,CACF,EACD,0BACF,CAAC,CAACT,MAAM,CAAC;EAETA,MAAM,GAAGH,mCAAmC,CAC1C,CACE;IACEK,QAAQ,EAAE,uCAAuC;IACjDC,eAAe,EAAGC,IAAI,IAAK;MACzB,IAAI,CAACA,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAE;QACpC,OAAOC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC;MAC3B;MACA,OAAOD,IAAI,CAACC,SAAS,CAACJ,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAEG,kBAAkB,IAAI,EAAE,CAAC;IAClF;EACF,CAAC,CACF,EACD,sBACF,CAAC,CAACT,MAAM,CAAC;EAET,OAAOA,MAAM;AACf,CAAC;AAACU,OAAA,CAAAX,iBAAA,GAAAA,iBAAA;AAAA,IAAAY,QAAA,GAAAD,OAAA,CAAAE,OAAA,GAEab,iBAAiB","ignoreList":[]} \ No newline at end of file +{"version":3,"file":"withInlineModules.js","names":["_configPlugins","data","require","createBuildGradlePropsConfigPlugin","AndroidConfig","BuildProperties","createBuildPodfilePropsConfigPlugin","IOSConfig","withInlineModules","config","props","propName","propValueGetter","conf","experiments","inlineModules","JSON","stringify","watchedDirectories","exports","_default","default"],"sources":["../../../../src/plugins/unversioned/expo-inline-modules/withInlineModules.ts"],"sourcesContent":["import { AndroidConfig, IOSConfig } from '@expo/config-plugins';\nimport type { ExpoConfig } from '@expo/config-types';\n\nconst { createBuildGradlePropsConfigPlugin } = AndroidConfig.BuildProperties;\nconst { createBuildPodfilePropsConfigPlugin } = IOSConfig.BuildProperties;\n\nexport const withInlineModules = (config: ExpoConfig, props: any) => {\n config = createBuildGradlePropsConfigPlugin(\n [\n {\n propName: 'expo.inlineModules.watchedDirectories',\n propValueGetter: (conf) => {\n if (!conf.experiments?.inlineModules) {\n return JSON.stringify([]);\n }\n return JSON.stringify(conf.experiments?.inlineModules?.watchedDirectories ?? []);\n },\n },\n ],\n 'withAndroidInlineModules'\n )(config);\n\n config = createBuildPodfilePropsConfigPlugin(\n [\n {\n propName: 'expo.inlineModules.watchedDirectories',\n propValueGetter: (conf) => {\n if (!conf.experiments?.inlineModules) {\n return JSON.stringify([]);\n }\n return JSON.stringify(conf.experiments?.inlineModules?.watchedDirectories ?? []);\n },\n },\n ],\n 'withIosInlineModules'\n )(config);\n\n return config;\n};\n\nexport default withInlineModules;\n"],"mappings":";;;;;;AAAA,SAAAA,eAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,cAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAGA,MAAM;EAAEE;AAAmC,CAAC,GAAGC,8BAAa,CAACC,eAAe;AAC5E,MAAM;EAAEC;AAAoC,CAAC,GAAGC,0BAAS,CAACF,eAAe;AAElE,MAAMG,iBAAiB,GAAGA,CAACC,MAAkB,EAAEC,KAAU,KAAK;EACnED,MAAM,GAAGN,kCAAkC,CACzC,CACE;IACEQ,QAAQ,EAAE,uCAAuC;IACjDC,eAAe,EAAGC,IAAI,IAAK;MACzB,IAAI,CAACA,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAE;QACpC,OAAOC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC;MAC3B;MACA,OAAOD,IAAI,CAACC,SAAS,CAACJ,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAEG,kBAAkB,IAAI,EAAE,CAAC;IAClF;EACF,CAAC,CACF,EACD,0BACF,CAAC,CAACT,MAAM,CAAC;EAETA,MAAM,GAAGH,mCAAmC,CAC1C,CACE;IACEK,QAAQ,EAAE,uCAAuC;IACjDC,eAAe,EAAGC,IAAI,IAAK;MACzB,IAAI,CAACA,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAE;QACpC,OAAOC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC;MAC3B;MACA,OAAOD,IAAI,CAACC,SAAS,CAACJ,IAAI,CAACC,WAAW,EAAEC,aAAa,EAAEG,kBAAkB,IAAI,EAAE,CAAC;IAClF;EACF,CAAC,CACF,EACD,sBACF,CAAC,CAACT,MAAM,CAAC;EAET,OAAOA,MAAM;AACf,CAAC;AAACU,OAAA,CAAAX,iBAAA,GAAAA,iBAAA;AAAA,IAAAY,QAAA,GAAAD,OAAA,CAAAE,OAAA,GAEab,iBAAiB","ignoreList":[]} \ No newline at end of file diff --git a/packages/@expo/prebuild-config/package.json b/packages/@expo/prebuild-config/package.json index ca0832f0c191da..c54f317c2128e9 100644 --- a/packages/@expo/prebuild-config/package.json +++ b/packages/@expo/prebuild-config/package.json @@ -30,9 +30,6 @@ "files": [ "build" ], - "peerDependencies": { - "expo": "workspace:*" - }, "devDependencies": { "@babel/cli": "^7.23.4", "@expo/plist": "workspace:*", @@ -48,6 +45,7 @@ "@expo/image-utils": "workspace:^0.8.12", "@expo/json-file": "workspace:^10.0.12", "@react-native/normalize-colors": "0.85.2", + "expo-modules-autolinking": "workspace:~55.0.8", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0" diff --git a/packages/@expo/prebuild-config/src/getAutolinkedPackages.ts b/packages/@expo/prebuild-config/src/getAutolinkedPackages.ts index 9c082fea988db1..85b39691a26165 100644 --- a/packages/@expo/prebuild-config/src/getAutolinkedPackages.ts +++ b/packages/@expo/prebuild-config/src/getAutolinkedPackages.ts @@ -3,7 +3,7 @@ import type { ExpoConfig } from '@expo/config-types'; import { makeCachedDependenciesLinker, scanExpoModuleResolutionsForPlatform, -} from 'expo/internal/unstable-autolinking-exports'; +} from 'expo-modules-autolinking/exports'; /** * Returns a list of packages that are autolinked to a project. diff --git a/packages/@expo/prebuild-config/src/plugins/unversioned/expo-inline-modules/withInlineModules.ts b/packages/@expo/prebuild-config/src/plugins/unversioned/expo-inline-modules/withInlineModules.ts index abfb4054c70478..9c577d821c016d 100644 --- a/packages/@expo/prebuild-config/src/plugins/unversioned/expo-inline-modules/withInlineModules.ts +++ b/packages/@expo/prebuild-config/src/plugins/unversioned/expo-inline-modules/withInlineModules.ts @@ -1,5 +1,6 @@ +import { AndroidConfig, IOSConfig } from '@expo/config-plugins'; import type { ExpoConfig } from '@expo/config-types'; -import { AndroidConfig, IOSConfig } from 'expo/config-plugins'; + const { createBuildGradlePropsConfigPlugin } = AndroidConfig.BuildProperties; const { createBuildPodfilePropsConfigPlugin } = IOSConfig.BuildProperties; diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt index cfe6a03bee8612..5da0dc3c42640d 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt @@ -54,8 +54,8 @@ class AppMetricsModule : Module(), UpdatesStateChangeListener { AppStartupManager.markFirstRender() } - Function("markInteractive") { routeName: String? -> - AppStartupManager.markInteractive(routeName) + Function("markInteractive") { attributes: MetricAttributes? -> + AppStartupManager.markInteractive(attributes?.routeName, attributes?.params) scope.launch { saveStartupMetricsIfNotSaved() @@ -186,3 +186,8 @@ data class PartialMetric( params = params?.let { Json.encodeToString(it) } ) } + +data class MetricAttributes( + @Field val routeName: String? = null, + @Field val params: Map? = null +) : Record diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt index ce7c4c1acf2eb7..de7cef69fdddad 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt @@ -203,22 +203,22 @@ object AppStartupManager { } } - fun markInteractive(routeName: String? = null) { + fun markInteractive(routeName: String? = null, params: Map? = null) { if (startupState != StartupState.LAUNCHING || hasRecordedInteractive) return hasRecordedInteractive = true val frameMetrics = frameMetricsRecorder.stop() - val params = if (frameMetrics.expectedFrames > 0) { - mapOf( + val mergedParams = if (frameMetrics.expectedFrames > 0) { + params.orEmpty() + mapOf( "frameRate.slowFrames" to frameMetrics.slowFrames, "frameRate.frozenFrames" to frameMetrics.frozenFrames, "frameRate.totalDelay" to frameMetrics.freezeTimeMs.toDouble() / 1000.0 ) } else { - null + params } - addMetricSinceLaunch(AppStartupMetric.TimeToInteractive, routeName, params) + addMetricSinceLaunch(AppStartupMetric.TimeToInteractive, routeName, mergedParams) startupState = StartupState.LAUNCHED } diff --git a/packages/expo-app-metrics/build/module.d.ts b/packages/expo-app-metrics/build/module.d.ts index dc13419e280731..81bd85d9e7be11 100644 --- a/packages/expo-app-metrics/build/module.d.ts +++ b/packages/expo-app-metrics/build/module.d.ts @@ -1,5 +1,6 @@ +import type { MetricAttributes } from './types'; declare const _default: { - markInteractive(): void; + markInteractive(attributes?: MetricAttributes): void; markFirstRender(): void; getStoredEntries(): Promise; clearStoredEntries(): Promise; diff --git a/packages/expo-app-metrics/build/module.d.ts.map b/packages/expo-app-metrics/build/module.d.ts.map index 26fd452cd86c3c..ffcebb7f7ff87e 100644 --- a/packages/expo-app-metrics/build/module.d.ts.map +++ b/packages/expo-app-metrics/build/module.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":";;;;;;;;;;;iBAsB43F,CAAC;cAAsB,CAAC;;;AAbp5F,wBAYE"} \ No newline at end of file +{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA4B,gBAAgB,EAAE,MAAM,SAAS,CAAC;;iCAQ3C,gBAAgB;;;;;;;;;;iBAU6qG,CAAC;cAAsB,CAAC;;;AAZpvG,wBAWE"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.js b/packages/expo-app-metrics/build/module.js index a9020ce9f4e8c8..83ece0d474e90a 100644 --- a/packages/expo-app-metrics/build/module.js +++ b/packages/expo-app-metrics/build/module.js @@ -4,15 +4,14 @@ const NativeModule = requireNativeModule('ExpoAppMetrics'); initRouterNavigationEvents(); export default { ...NativeModule, - markInteractive() { - const routeName = getInitialRouteName(); + markInteractive(attributes) { // If the markInteractive is called before the first render, we mark both events. // Otherwise the markFirstRender would not mark native event. // This can happen in two scenarios: // 1. User calls markInteractive manually before the first render. // 2. User calls markInteractive in child's useEffect, while the first render is marked in parent's useEffect. NativeModule.markFirstRender(); - NativeModule.markInteractive(routeName); + NativeModule.markInteractive({ routeName: getInitialRouteName(), ...attributes }); }, }; //# sourceMappingURL=module.js.map \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.js.map b/packages/expo-app-metrics/build/module.js.map index 84f50f3d3b9437..cde4d77cb20c2e 100644 --- a/packages/expo-app-metrics/build/module.js.map +++ b/packages/expo-app-metrics/build/module.js.map @@ -1 +1 @@ -{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAGtF,MAAM,YAAY,GAAG,mBAAmB,CAA2B,gBAAgB,CAAC,CAAC;AAErF,0BAA0B,EAAE,CAAC;AAE7B,eAAe;IACb,GAAG,YAAY;IACf,eAAe;QACb,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;QACxC,iFAAiF;QACjF,6DAA6D;QAC7D,oCAAoC;QACpC,kEAAkE;QAClE,8GAA8G;QAC9G,YAAY,CAAC,eAAe,EAAE,CAAC;QAC/B,YAAY,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC;CACF,CAAC","sourcesContent":["import { requireNativeModule } from 'expo';\n\nimport { getInitialRouteName, initRouterNavigationEvents } from './routerIntegration';\nimport type { ExpoAppMetricsModuleType } from './types';\n\nconst NativeModule = requireNativeModule('ExpoAppMetrics');\n\ninitRouterNavigationEvents();\n\nexport default {\n ...NativeModule,\n markInteractive() {\n const routeName = getInitialRouteName();\n // If the markInteractive is called before the first render, we mark both events.\n // Otherwise the markFirstRender would not mark native event.\n // This can happen in two scenarios:\n // 1. User calls markInteractive manually before the first render.\n // 2. User calls markInteractive in child's useEffect, while the first render is marked in parent's useEffect.\n NativeModule.markFirstRender();\n NativeModule.markInteractive(routeName);\n },\n};\n"]} \ No newline at end of file +{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAGtF,MAAM,YAAY,GAAG,mBAAmB,CAA2B,gBAAgB,CAAC,CAAC;AAErF,0BAA0B,EAAE,CAAC;AAE7B,eAAe;IACb,GAAG,YAAY;IACf,eAAe,CAAC,UAA6B;QAC3C,iFAAiF;QACjF,6DAA6D;QAC7D,oCAAoC;QACpC,kEAAkE;QAClE,8GAA8G;QAC9G,YAAY,CAAC,eAAe,EAAE,CAAC;QAC/B,YAAY,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,mBAAmB,EAAE,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IACpF,CAAC;CACF,CAAC","sourcesContent":["import { requireNativeModule } from 'expo';\n\nimport { getInitialRouteName, initRouterNavigationEvents } from './routerIntegration';\nimport type { ExpoAppMetricsModuleType, MetricAttributes } from './types';\n\nconst NativeModule = requireNativeModule('ExpoAppMetrics');\n\ninitRouterNavigationEvents();\n\nexport default {\n ...NativeModule,\n markInteractive(attributes?: MetricAttributes) {\n // If the markInteractive is called before the first render, we mark both events.\n // Otherwise the markFirstRender would not mark native event.\n // This can happen in two scenarios:\n // 1. User calls markInteractive manually before the first render.\n // 2. User calls markInteractive in child's useEffect, while the first render is marked in parent's useEffect.\n NativeModule.markFirstRender();\n NativeModule.markInteractive({ routeName: getInitialRouteName(), ...attributes });\n },\n};\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.web.d.ts b/packages/expo-app-metrics/build/module.web.d.ts index 6703fe4db61376..a6eddcaac81234 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts +++ b/packages/expo-app-metrics/build/module.web.d.ts @@ -1,5 +1,5 @@ import { NativeModule } from 'expo'; -import type { ExpoAppMetricsModuleType } from './types'; +import type { ExpoAppMetricsModuleType, MetricAttributes } from './types'; export * from './types'; declare class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType { addCustomMetricToSession(sessionId: string, metric: { @@ -8,7 +8,7 @@ declare class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetric value: number; }): Promise; markFirstRender(): Promise; - markInteractive(): Promise; + markInteractive(_attributes?: MetricAttributes): Promise; getStoredEntries(): Promise; clearStoredEntries(): Promise; startSession(metadata?: string): string; diff --git a/packages/expo-app-metrics/build/module.web.d.ts.map b/packages/expo-app-metrics/build/module.web.d.ts.map index fb97efc4987ddf..7d08740bb8f667 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts.map +++ b/packages/expo-app-metrics/build/module.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAExD,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC;IAGV,eAAe;IACf,eAAe;IACf,gBAAgB;IAGhB,kBAAkB;IACxB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAG9B,WAAW,CAAC,SAAS,EAAE,MAAM;CAC9B;;AAED,wBAAyE"} \ No newline at end of file +{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE1E,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC;IAGV,eAAe;IACf,eAAe,CAAC,WAAW,CAAC,EAAE,gBAAgB;IAC9C,gBAAgB;IAGhB,kBAAkB;IACxB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAG9B,WAAW,CAAC,SAAS,EAAE,MAAM;CAC9B;;AAED,wBAAyE"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.web.js b/packages/expo-app-metrics/build/module.web.js index 98a5fd305e25fa..76c8f3f8e3099c 100644 --- a/packages/expo-app-metrics/build/module.web.js +++ b/packages/expo-app-metrics/build/module.web.js @@ -5,7 +5,7 @@ class ExpoAppMetricsModule extends NativeModule { throw new Error('Method not implemented.'); } async markFirstRender() { } - async markInteractive() { } + async markInteractive(_attributes) { } async getStoredEntries() { return []; } diff --git a/packages/expo-app-metrics/build/module.web.js.map b/packages/expo-app-metrics/build/module.web.js.map index dd4495a7a9e70f..f8f4a65ddd45fc 100644 --- a/packages/expo-app-metrics/build/module.web.js.map +++ b/packages/expo-app-metrics/build/module.web.js.map @@ -1 +1 @@ -{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CACtB,SAAiB,EACjB,MAAyD;QAEzD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,YAAY,CAAC,QAAiB;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,WAAW,CAAC,SAAiB,IAAG,CAAC;CAClC;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(\n sessionId: string,\n metric: { category: string; name: string; value: number }\n ): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive() {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n startSession(metadata?: string) {\n return '';\n }\n stopSession(sessionId: string) {}\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file +{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CACtB,SAAiB,EACjB,MAAyD;QAEzD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,CAAC,WAA8B,IAAG,CAAC;IACxD,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,YAAY,CAAC,QAAiB;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,WAAW,CAAC,SAAiB,IAAG,CAAC;CAClC;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType, MetricAttributes } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(\n sessionId: string,\n metric: { category: string; name: string; value: number }\n ): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive(_attributes?: MetricAttributes) {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n startSession(metadata?: string) {\n return '';\n }\n stopSession(sessionId: string) {}\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.d.ts b/packages/expo-app-metrics/build/types.d.ts index 4ca0b908cdc7db..ca68825a6404f7 100644 --- a/packages/expo-app-metrics/build/types.d.ts +++ b/packages/expo-app-metrics/build/types.d.ts @@ -95,9 +95,21 @@ export interface Metric { routeName?: string; params?: Record; } +export type MetricAttributes = { + /** + * Name of the route associated with the metric. Some metrics populate this + * with a sensible default when omitted — for example, the TTI metric falls + * back to the initial route name detected from the router. + */ + routeName?: string; + /** + * Custom parameters to attach to the metric. + */ + params?: Record; +}; export interface ExpoAppMetricsModuleType { markFirstRender(): void; - markInteractive(routeName?: string): void; + markInteractive(attributes?: MetricAttributes): void; getStoredEntries(): Promise; clearStoredEntries(): Promise; /** diff --git a/packages/expo-app-metrics/build/types.d.ts.map b/packages/expo-app-metrics/build/types.d.ts.map index eadab86a750e69..8b821f143ff8e8 100644 --- a/packages/expo-app-metrics/build/types.d.ts.map +++ b/packages/expo-app-metrics/build/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,IAAI,IAAI,CAAC;IACxB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;;;OAIG;IACH,YAAY,IAAI,MAAM,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;OAEG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,GACA,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,eAAe,IAAI,IAAI,CAAC;IACxB,eAAe,CAAC,UAAU,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrD,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;;;OAIG;IACH,YAAY,IAAI,MAAM,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;OAEG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,GACA,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.js.map b/packages/expo-app-metrics/build/types.js.map index a7afb46ea30f3a..f74547d9a492a0 100644 --- a/packages/expo-app-metrics/build/types.js.map +++ b/packages/expo-app-metrics/build/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(routeName?: string): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n\n /**\n * Starts a new app metrics session. Returns the session ID.\n *\n * @platform android\n */\n startSession(): string;\n /**\n * Stops the app metrics session with the given session ID.\n *\n * @platform android\n */\n stopSession(sessionId: string): void;\n /**\n * @platform android\n */\n addCustomMetricToSession(\n sessionId: string,\n metric: {\n category: string;\n name: string;\n value: number;\n routeName?: string;\n params?: Record;\n }\n ): Promise;\n}\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport type MetricAttributes = {\n /**\n * Name of the route associated with the metric. Some metrics populate this\n * with a sensible default when omitted — for example, the TTI metric falls\n * back to the initial route name detected from the router.\n */\n routeName?: string;\n /**\n * Custom parameters to attach to the metric.\n */\n params?: Record;\n};\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(attributes?: MetricAttributes): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n\n /**\n * Starts a new app metrics session. Returns the session ID.\n *\n * @platform android\n */\n startSession(): string;\n /**\n * Stops the app metrics session with the given session ID.\n *\n * @platform android\n */\n stopSession(sessionId: string): void;\n /**\n * @platform android\n */\n addCustomMetricToSession(\n sessionId: string,\n metric: {\n category: string;\n name: string;\n value: number;\n routeName?: string;\n params?: Record;\n }\n ): Promise;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/ios/AppMetricsModule.swift b/packages/expo-app-metrics/ios/AppMetricsModule.swift index 5978d7b09b6720..01b0c9beca6b01 100644 --- a/packages/expo-app-metrics/ios/AppMetricsModule.swift +++ b/packages/expo-app-metrics/ios/AppMetricsModule.swift @@ -26,8 +26,11 @@ public final class AppMetricsModule: Module, UpdatesStateChangeListener { AppMetrics.mainSession.appStartupMonitor.markFirstRender() } - Function("markInteractive") { (routeName: String?) in - AppMetrics.mainSession.appStartupMonitor.markInteractive(routeName: routeName) + Function("markInteractive") { (attributes: MetricAttributes?) in + AppMetrics.mainSession.appStartupMonitor.markInteractive( + routeName: attributes?.routeName, + params: attributes?.params ?? [:] + ) } AsyncFunction("getAppStartupTimesAsync") { @@ -67,3 +70,8 @@ public final class AppMetricsModule: Module, UpdatesStateChangeListener { } } } + +struct MetricAttributes: Record { + @Field var routeName: String? + @Field var params: [String: Any]? +} diff --git a/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift b/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift index 6d9edd19a2b60c..761270785311b6 100644 --- a/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift +++ b/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift @@ -85,11 +85,12 @@ final class AppStartupMonitoring: MetricReporter, @unchecked Sendable { } } - nonisolated func markInteractive(routeName: String? = nil) { + nonisolated func markInteractive(routeName: String? = nil, params: [String: Any] = [:]) { if launchType == .prewarmed { return } let currentTime = CACurrentMediaTime() + nonisolated(unsafe) let params = params AppMetricsActor.isolated { [self] in if startupState != .launching { @@ -101,21 +102,19 @@ final class AppStartupMonitoring: MetricReporter, @unchecked Sendable { markers.timeToInteractive = currentTime if let tti = markers.getTTI() { - var params: [String: Any]? + var params = params let frameMetrics = frameMetricsRecorder.stop() if frameMetrics.expectedFrames > 0 { - params = [ - "frameRate.slowFrames": frameMetrics.slowFrames, - "frameRate.frozenFrames": frameMetrics.frozenFrames, - "frameRate.totalDelay": frameMetrics.freezeTime, - ] + params["frameRate.slowFrames"] = frameMetrics.slowFrames + params["frameRate.frozenFrames"] = frameMetrics.frozenFrames + params["frameRate.totalDelay"] = frameMetrics.freezeTime } let metric = Metric( category: .appStartup, name: "timeToInteractive", value: tti, routeName: routeName, - params: params + params: params.isEmpty ? nil : params ) reportMetric(metric) } diff --git a/packages/expo-app-metrics/ios/Sessions/MainSession.swift b/packages/expo-app-metrics/ios/Sessions/MainSession.swift index f32c3f4783638c..37896792bbe0fd 100644 --- a/packages/expo-app-metrics/ios/Sessions/MainSession.swift +++ b/packages/expo-app-metrics/ios/Sessions/MainSession.swift @@ -4,7 +4,7 @@ Main session starts from launching the app to its termination. Some metrics like the app startup can only be tracked once and globally. In the future this class will also hold subsessions such as for time spent on a specific screen/route or user-initiated sessions. */ -internal final class MainSession: Session, @unchecked Sendable { +public final class MainSession: Session, @unchecked Sendable { let appStartupMonitor = AppStartupMonitoring() let updatesMonitor = UpdatesMonitoring() let frameMetricsRecorder = FrameMetricsRecorder() diff --git a/packages/expo-app-metrics/ios/Tests/AppStartupMonitoringTests.swift b/packages/expo-app-metrics/ios/Tests/AppStartupMonitoringTests.swift new file mode 100644 index 00000000000000..a9ec031af4ae91 --- /dev/null +++ b/packages/expo-app-metrics/ios/Tests/AppStartupMonitoringTests.swift @@ -0,0 +1,104 @@ +import Testing + +@testable import ExpoAppMetrics + +@AppMetricsActor +@Suite("AppStartupMonitoring") +struct AppStartupMonitoringTests { + private final class CapturingReceiver: MetricsReceiver, @unchecked Sendable { + var metrics: [Metric] = [] + + @AppMetricsActor + func receiveMetric(_ metric: Metric) { + metrics.append(metric) + } + } + + // Target frame duration for a 60fps display + private let target: TimeInterval = 0.016 + + private func frame(at timestamp: TimeInterval) -> Frame { + return Frame(timestamp: timestamp, targetTimestamp: timestamp + target, duration: target) + } + + private func makeMonitoring(receiver: CapturingReceiver) -> AppStartupMonitoring { + let monitoring = AppStartupMonitoring() + monitoring.launchType = .cold + monitoring.markers.finishedLaunching = 0 + monitoring.addReceiver(receiver) + return monitoring + } + + private func ttiMetric(from receiver: CapturingReceiver) async throws -> Metric { + // Both `markInteractive` and `reportMetric` dispatch via `Task {}`, + // so we need to wait for them to run before asserting. + for _ in 0..<20 { + if let metric = receiver.metrics.first(where: { $0.name == "timeToInteractive" }) { + return metric + } + try await Task.sleep(for: .milliseconds(10)) + } + Issue.record("Expected a timeToInteractive metric to be reported") + throw CancellationError() + } + + @Test + func `forwards user-supplied params to the emitted metric`() async throws { + let receiver = CapturingReceiver() + let monitoring = makeMonitoring(receiver: receiver) + + monitoring.markInteractive(routeName: "home", params: ["tenant": "acme", "cohort": 3]) + + let metric = try await ttiMetric(from: receiver) + let params = try #require(metric.params?.value as? [String: Any]) + #expect(params["tenant"] as? String == "acme") + #expect(params["cohort"] as? Int == 3) + #expect(metric.routeName == "home") + } + + @Test + func `emits nil params when no user params are provided and no frames were recorded`() async throws { + let receiver = CapturingReceiver() + let monitoring = makeMonitoring(receiver: receiver) + + monitoring.markInteractive() + + let metric = try await ttiMetric(from: receiver) + #expect(metric.params == nil) + } + + @Test + func `overlays frame metrics on top of user params`() async throws { + let receiver = CapturingReceiver() + let monitoring = makeMonitoring(receiver: receiver) + + // Simulate one frozen frame so the recorder produces non-zero metrics. + monitoring.frameMetricsRecorder.processFrame(frame(at: 1.0)) + monitoring.frameMetricsRecorder.processFrame(frame(at: 1.8)) + + monitoring.markInteractive(params: ["tenant": "acme"]) + + let metric = try await ttiMetric(from: receiver) + let params = try #require(metric.params?.value as? [String: Any]) + #expect(params["tenant"] as? String == "acme") + #expect(params["frameRate.slowFrames"] != nil) + #expect(params["frameRate.frozenFrames"] != nil) + #expect(params["frameRate.totalDelay"] != nil) + } + + @Test + func `frame metrics overwrite user-supplied frameRate keys on collision`() async throws { + let receiver = CapturingReceiver() + let monitoring = makeMonitoring(receiver: receiver) + + monitoring.frameMetricsRecorder.processFrame(frame(at: 1.0)) + monitoring.frameMetricsRecorder.processFrame(frame(at: 1.8)) + + monitoring.markInteractive(params: ["frameRate.slowFrames": 999]) + + let metric = try await ttiMetric(from: receiver) + let params = try #require(metric.params?.value as? [String: Any]) + // The actual frame recorder produces slowFrames == 1, not 999. + #expect((params["frameRate.slowFrames"] as? Int) != 999) + } +} diff --git a/packages/expo-app-metrics/src/module.ts b/packages/expo-app-metrics/src/module.ts index 5e69886e4cb8f8..40ddc710faa742 100644 --- a/packages/expo-app-metrics/src/module.ts +++ b/packages/expo-app-metrics/src/module.ts @@ -1,7 +1,7 @@ import { requireNativeModule } from 'expo'; import { getInitialRouteName, initRouterNavigationEvents } from './routerIntegration'; -import type { ExpoAppMetricsModuleType } from './types'; +import type { ExpoAppMetricsModuleType, MetricAttributes } from './types'; const NativeModule = requireNativeModule('ExpoAppMetrics'); @@ -9,14 +9,13 @@ initRouterNavigationEvents(); export default { ...NativeModule, - markInteractive() { - const routeName = getInitialRouteName(); + markInteractive(attributes?: MetricAttributes) { // If the markInteractive is called before the first render, we mark both events. // Otherwise the markFirstRender would not mark native event. // This can happen in two scenarios: // 1. User calls markInteractive manually before the first render. // 2. User calls markInteractive in child's useEffect, while the first render is marked in parent's useEffect. NativeModule.markFirstRender(); - NativeModule.markInteractive(routeName); + NativeModule.markInteractive({ routeName: getInitialRouteName(), ...attributes }); }, }; diff --git a/packages/expo-app-metrics/src/module.web.ts b/packages/expo-app-metrics/src/module.web.ts index ceec669b6e87e2..34a4eb0c98ba76 100644 --- a/packages/expo-app-metrics/src/module.web.ts +++ b/packages/expo-app-metrics/src/module.web.ts @@ -1,6 +1,6 @@ import { NativeModule, registerWebModule } from 'expo'; -import type { ExpoAppMetricsModuleType } from './types'; +import type { ExpoAppMetricsModuleType, MetricAttributes } from './types'; export * from './types'; @@ -12,7 +12,7 @@ class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleT throw new Error('Method not implemented.'); } async markFirstRender() {} - async markInteractive() {} + async markInteractive(_attributes?: MetricAttributes) {} async getStoredEntries() { return []; } diff --git a/packages/expo-app-metrics/src/types.ts b/packages/expo-app-metrics/src/types.ts index a8020bae779ebb..a72df34a421162 100644 --- a/packages/expo-app-metrics/src/types.ts +++ b/packages/expo-app-metrics/src/types.ts @@ -99,9 +99,22 @@ export interface Metric { params?: Record; } +export type MetricAttributes = { + /** + * Name of the route associated with the metric. Some metrics populate this + * with a sensible default when omitted — for example, the TTI metric falls + * back to the initial route name detected from the router. + */ + routeName?: string; + /** + * Custom parameters to attach to the metric. + */ + params?: Record; +}; + export interface ExpoAppMetricsModuleType { markFirstRender(): void; - markInteractive(routeName?: string): void; + markInteractive(attributes?: MetricAttributes): void; getStoredEntries(): Promise; clearStoredEntries(): Promise; diff --git a/packages/expo-camera/CHANGELOG.md b/packages/expo-camera/CHANGELOG.md index 6c1230612d2dbf..907fc158573136 100644 --- a/packages/expo-camera/CHANGELOG.md +++ b/packages/expo-camera/CHANGELOG.md @@ -16,7 +16,7 @@ - [iOS] Use runtime camera availability checks in Simulator so camera code paths can run when a runtime video device is present while preserving fallback behavior when no device is available. (by [@kmagiera](https://github.com/kmagiera)) ([#44159](https://github.com/expo/expo/pull/44159) by [@kmagiera](https://github.com/kmagiera)) - [iOS] Fix orientation issue caused by upstream changes. ([#44171](https://github.com/expo/expo/pull/44171) by [@alanjhughes](https://github.com/alanjhughes)) - [iOS] Fix inconsistent barcode `type` value returned by ZXing fallback scanner (code39, pdf417, codabar) — it now returns the same format as the AVFoundation scanner (e.g. `"code39"` instead of `"org.iso.Code39"`). ([#44726](https://github.com/expo/expo/pull/44726) by [@jensdev](https://github.com/jensdev)) - +- [iOS] Fix camera activity indicator remaining active after `CameraView.launchScanner` is interactively dismissed and the app is backgrounded/foregrounded. ([#45063](https://github.com/expo/expo/pull/45063) by [@alanjhughes](https://github.com/alanjhughes)) ### 💡 Others diff --git a/packages/expo-camera/ios/CameraViewModule.swift b/packages/expo-camera/ios/CameraViewModule.swift index a32f11f8800d26..03c82a4f55fe38 100644 --- a/packages/expo-camera/ios/CameraViewModule.swift +++ b/packages/expo-camera/ios/CameraViewModule.swift @@ -405,7 +405,10 @@ public final class CameraViewModule: Module, ScannerResultHandler { controller.delegate = delegate } - appContext?.utilities?.currentViewController()?.present(controller, animated: true) { + appContext?.utilities?.currentViewController()?.present(controller, animated: true) { [weak self] in + if let delegate = self?.scannerContext?.delegate as? VisionScannerDelegate { + controller.presentationController?.delegate = delegate + } try? controller.startScanning() } } @@ -416,14 +419,25 @@ public final class CameraViewModule: Module, ScannerResultHandler { guard let controller = scannerContext?.controller as? DataScannerViewController else { return } - controller.stopScanning() - controller.dismiss(animated: true) + controller.dismiss(animated: true) { [weak self] in + self?.onScannerDismissed() + } } func onItemScanned(result: [String: Any]) { sendEvent("onModernBarcodeScanned", result) } + @MainActor + func onScannerDismissed() { + if #available(iOS 16.0, *) { + if let controller = scannerContext?.controller as? DataScannerViewController { + controller.stopScanning() + } + } + scannerContext = nil + } + private func getAvailableVideoCodecs() -> [String] { let session = AVCaptureSession() diff --git a/packages/expo-camera/ios/Current/BarcodeScannerUtils.swift b/packages/expo-camera/ios/Current/BarcodeScannerUtils.swift index 486a4e89fe0573..23827cfcfd0004 100644 --- a/packages/expo-camera/ios/Current/BarcodeScannerUtils.swift +++ b/packages/expo-camera/ios/Current/BarcodeScannerUtils.swift @@ -18,13 +18,10 @@ class BarcodeScannerUtils { "aztec": AVMetadataObject.ObjectType.aztec, "interleaved2of5": AVMetadataObject.ObjectType.interleaved2of5, "itf14": AVMetadataObject.ObjectType.itf14, - "datamatrix": AVMetadataObject.ObjectType.dataMatrix + "datamatrix": AVMetadataObject.ObjectType.dataMatrix, + "codabar": AVMetadataObject.ObjectType.codabar ] - if #available(iOS 15.4, *) { - validTypes["codabar"] = AVMetadataObject.ObjectType.codabar - } - return [BARCODE_TYPES_KEY: Array(validTypes.values)] } diff --git a/packages/expo-camera/ios/Current/VisionScannerDelegate.swift b/packages/expo-camera/ios/Current/VisionScannerDelegate.swift index 0e5ea63419f505..f7ceabca264f0b 100644 --- a/packages/expo-camera/ios/Current/VisionScannerDelegate.swift +++ b/packages/expo-camera/ios/Current/VisionScannerDelegate.swift @@ -3,10 +3,11 @@ import ExpoModulesCore protocol ScannerResultHandler { func onItemScanned(result: [String: Any]) + @MainActor func onScannerDismissed() } @available(iOS 16.0, *) -class VisionScannerDelegate: NSObject, DataScannerViewControllerDelegate { +class VisionScannerDelegate: NSObject, DataScannerViewControllerDelegate, UIAdaptivePresentationControllerDelegate { private let handler: ScannerResultHandler init(handler: ScannerResultHandler) { @@ -26,4 +27,8 @@ class VisionScannerDelegate: NSObject, DataScannerViewControllerDelegate { } } } + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + handler.onScannerDismissed() + } } diff --git a/packages/expo-document-picker/build/types.d.ts b/packages/expo-document-picker/build/types.d.ts index a7d727b1e039a2..e27ab0b83528d8 100644 --- a/packages/expo-document-picker/build/types.d.ts +++ b/packages/expo-document-picker/build/types.d.ts @@ -40,7 +40,7 @@ export type DocumentPickerAsset = { */ size?: number; /** - * An URI to the local document file. + * A URI to the local document file. */ uri: string; /** diff --git a/packages/expo-document-picker/build/types.js.map b/packages/expo-document-picker/build/types.js.map index 412f43fa9f890d..ea531a8719c8f4 100644 --- a/packages/expo-document-picker/build/types.js.map +++ b/packages/expo-document-picker/build/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["// @needsAudit\nexport type DocumentPickerOptions = {\n /**\n * The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\n * to be picked. It also supports wildcards like `'image/*'` to choose any image. To allow any type\n * of document you can use `'*/*'`.\n * @default '*/*'\n */\n type?: string | string[];\n /**\n * If `true`, the picked file is copied to [`FileSystem.CacheDirectory`](./filesystem#filesystemcachedirectory),\n * which allows other Expo APIs to read the file immediately. This may impact performance for\n * large files, so you should consider setting this to `false` if you expect users to pick\n * particularly large files and your app does not need immediate read access.\n * @platform ios\n * @platform android\n * @default true\n */\n copyToCacheDirectory?: boolean;\n /**\n * Allows multiple files to be selected from the system UI.\n * @default false\n *\n */\n multiple?: boolean;\n /**\n * If `true`, asset url is base64 from the file\n * If `false`, asset url is the file url parameter\n * @platform web\n * @default true\n */\n base64?: boolean;\n};\n\nexport type DocumentPickerAsset = {\n /**\n * Document original name.\n */\n name: string;\n /**\n * Document size in bytes.\n */\n size?: number;\n /**\n * An URI to the local document file.\n */\n uri: string;\n /**\n * Document MIME type.\n */\n mimeType?: string;\n /**\n * Timestamp of last document modification. [Web API specs](https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified)\n * The lastModified provides the last modified date of the file as the number\n * of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files\n * without a known last modified date return the current date.\n */\n lastModified: number;\n /**\n * `File` object for the parity with web File API.\n * @platform web\n */\n file?: File;\n /**\n * Base64 string of the file.\n * @platform web\n */\n base64?: string;\n};\n\n/**\n * Type representing successful and canceled document pick result.\n */\nexport type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult;\n\n/**\n * Type representing successful pick result.\n */\nexport type DocumentPickerSuccessResult = {\n /**\n * If asset data have been returned this should always be `false`.\n */\n canceled: false;\n /**\n * An array of picked assets.\n */\n assets: DocumentPickerAsset[];\n /**\n * `FileList` object for the parity with web File API.\n * @platform web\n */\n output?: FileList;\n};\n\n/**\n * Type representing canceled pick result.\n */\nexport type DocumentPickerCanceledResult = {\n /**\n * Always `true` when the request was canceled.\n */\n canceled: true;\n /**\n * Always `null` when the request was canceled.\n */\n assets: null;\n /**\n * Always `null` when the request was canceled.\n * @platform web\n */\n output?: null;\n};\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["// @needsAudit\nexport type DocumentPickerOptions = {\n /**\n * The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\n * to be picked. It also supports wildcards like `'image/*'` to choose any image. To allow any type\n * of document you can use `'*/*'`.\n * @default '*/*'\n */\n type?: string | string[];\n /**\n * If `true`, the picked file is copied to [`FileSystem.CacheDirectory`](./filesystem#filesystemcachedirectory),\n * which allows other Expo APIs to read the file immediately. This may impact performance for\n * large files, so you should consider setting this to `false` if you expect users to pick\n * particularly large files and your app does not need immediate read access.\n * @platform ios\n * @platform android\n * @default true\n */\n copyToCacheDirectory?: boolean;\n /**\n * Allows multiple files to be selected from the system UI.\n * @default false\n *\n */\n multiple?: boolean;\n /**\n * If `true`, asset url is base64 from the file\n * If `false`, asset url is the file url parameter\n * @platform web\n * @default true\n */\n base64?: boolean;\n};\n\nexport type DocumentPickerAsset = {\n /**\n * Document original name.\n */\n name: string;\n /**\n * Document size in bytes.\n */\n size?: number;\n /**\n * A URI to the local document file.\n */\n uri: string;\n /**\n * Document MIME type.\n */\n mimeType?: string;\n /**\n * Timestamp of last document modification. [Web API specs](https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified)\n * The lastModified provides the last modified date of the file as the number\n * of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files\n * without a known last modified date return the current date.\n */\n lastModified: number;\n /**\n * `File` object for the parity with web File API.\n * @platform web\n */\n file?: File;\n /**\n * Base64 string of the file.\n * @platform web\n */\n base64?: string;\n};\n\n/**\n * Type representing successful and canceled document pick result.\n */\nexport type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult;\n\n/**\n * Type representing successful pick result.\n */\nexport type DocumentPickerSuccessResult = {\n /**\n * If asset data have been returned this should always be `false`.\n */\n canceled: false;\n /**\n * An array of picked assets.\n */\n assets: DocumentPickerAsset[];\n /**\n * `FileList` object for the parity with web File API.\n * @platform web\n */\n output?: FileList;\n};\n\n/**\n * Type representing canceled pick result.\n */\nexport type DocumentPickerCanceledResult = {\n /**\n * Always `true` when the request was canceled.\n */\n canceled: true;\n /**\n * Always `null` when the request was canceled.\n */\n assets: null;\n /**\n * Always `null` when the request was canceled.\n * @platform web\n */\n output?: null;\n};\n"]} \ No newline at end of file diff --git a/packages/expo-document-picker/src/types.ts b/packages/expo-document-picker/src/types.ts index a8176dbf1ca10c..8fc34f176ebb3c 100644 --- a/packages/expo-document-picker/src/types.ts +++ b/packages/expo-document-picker/src/types.ts @@ -42,7 +42,7 @@ export type DocumentPickerAsset = { */ size?: number; /** - * An URI to the local document file. + * A URI to the local document file. */ uri: string; /** diff --git a/packages/expo-font/CHANGELOG.md b/packages/expo-font/CHANGELOG.md index 2fdfb19f54e4c5..0bb91cd58fe39c 100644 --- a/packages/expo-font/CHANGELOG.md +++ b/packages/expo-font/CHANGELOG.md @@ -13,8 +13,12 @@ ### 🐛 Bug fixes +- Align `ServerFontResourceDescriptor.crossOrigin` type with React ([#45115](https://github.com/expo/expo/pull/45115) by [@hassankhan](https://github.com/hassankhan)) + ### 💡 Others +- Export `ServerFontResourceDescriptor` type for external usage ([#45116](https://github.com/expo/expo/pull/45116) by [@hassankhan](https://github.com/hassankhan)) + ## 55.0.4 — 2026-02-16 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-font/build/ExpoFontLoader.d.ts b/packages/expo-font/build/ExpoFontLoader.d.ts index f47142a5b640ae..604b603845e1ea 100644 --- a/packages/expo-font/build/ExpoFontLoader.d.ts +++ b/packages/expo-font/build/ExpoFontLoader.d.ts @@ -1,15 +1,4 @@ -import type { UnloadFontOptions } from './Font.types'; -export type ServerFontResourceDescriptor = { - type: 'style'; - css: string; - id: string; -} | { - type: 'link'; - as: 'font'; - crossOrigin?: string; - href: string; - rel: 'preload'; -}; +import type { ServerFontResourceDescriptor, UnloadFontOptions } from './Font.types'; export type ExpoFontLoaderModule = { getLoadedFonts: () => string[]; loadAsync: (fontFamilyName: string, localUriOrWebAsset: any) => Promise; diff --git a/packages/expo-font/build/ExpoFontLoader.d.ts.map b/packages/expo-font/build/ExpoFontLoader.d.ts.map index fa5b277a072a7b..c13412ebd7f27c 100644 --- a/packages/expo-font/build/ExpoFontLoader.d.ts.map +++ b/packages/expo-font/build/ExpoFontLoader.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ExpoFontLoader.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,MAAM,4BAA4B,GACpC;IACE,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,SAAS,CAAC;CAChB,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC5E,kBAAkB,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IACpC,4BAA4B,CAAC,EAAE,MAAM,4BAA4B,EAAE,CAAC;IACpE,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AAEF,QAAA,MAAM,CAAC,EAAE,oBAWkC,CAAC;AAC5C,eAAe,CAAC,CAAC"} \ No newline at end of file +{"version":3,"file":"ExpoFontLoader.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpF,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC5E,kBAAkB,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IACpC,4BAA4B,CAAC,EAAE,MAAM,4BAA4B,EAAE,CAAC;IACpE,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AAEF,QAAA,MAAM,CAAC,EAAE,oBAWkC,CAAC;AAC5C,eAAe,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/expo-font/build/ExpoFontLoader.js.map b/packages/expo-font/build/ExpoFontLoader.js.map index b371eb99209d9e..a4fdb3e217b3e1 100644 --- a/packages/expo-font/build/ExpoFontLoader.js.map +++ b/packages/expo-font/build/ExpoFontLoader.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpoFontLoader.js","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA8BxD,MAAM,CAAC,GACL,OAAO,MAAM,KAAK,WAAW;IAC3B,CAAC,CAAC,oBAAoB;QACpB;YACE,cAAc;gBACZ,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,SAAS;gBACP,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YAC3B,CAAC;SACF;IACH,CAAC,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;AAC5C,eAAe,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nimport type { UnloadFontOptions } from './Font.types';\n\nexport type ServerFontResourceDescriptor =\n | {\n type: 'style';\n css: string;\n id: string;\n }\n | {\n type: 'link';\n as: 'font';\n crossOrigin?: string;\n href: string;\n rel: 'preload';\n };\n\nexport type ExpoFontLoaderModule = {\n getLoadedFonts: () => string[];\n loadAsync: (fontFamilyName: string, localUriOrWebAsset: any) => Promise;\n // the following methods are only available on web\n unloadAllAsync?: () => Promise;\n unloadAsync?: (fontFamilyName: string, options?: UnloadFontOptions) => Promise;\n isLoaded?: (fontFamilyName: string, options?: UnloadFontOptions) => boolean;\n getServerResources?: () => string[];\n getServerResourceDescriptors?: () => ServerFontResourceDescriptor[];\n resetServerContext?: () => void;\n};\n\nconst m: ExpoFontLoaderModule =\n typeof window === 'undefined'\n ? // React server mock\n {\n getLoadedFonts() {\n return [];\n },\n loadAsync() {\n return Promise.resolve();\n },\n }\n : requireNativeModule('ExpoFontLoader');\nexport default m;\n"]} \ No newline at end of file +{"version":3,"file":"ExpoFontLoader.js","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgBxD,MAAM,CAAC,GACL,OAAO,MAAM,KAAK,WAAW;IAC3B,CAAC,CAAC,oBAAoB;QACpB;YACE,cAAc;gBACZ,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,SAAS;gBACP,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YAC3B,CAAC;SACF;IACH,CAAC,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;AAC5C,eAAe,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nimport type { ServerFontResourceDescriptor, UnloadFontOptions } from './Font.types';\n\nexport type ExpoFontLoaderModule = {\n getLoadedFonts: () => string[];\n loadAsync: (fontFamilyName: string, localUriOrWebAsset: any) => Promise;\n // the following methods are only available on web\n unloadAllAsync?: () => Promise;\n unloadAsync?: (fontFamilyName: string, options?: UnloadFontOptions) => Promise;\n isLoaded?: (fontFamilyName: string, options?: UnloadFontOptions) => boolean;\n getServerResources?: () => string[];\n getServerResourceDescriptors?: () => ServerFontResourceDescriptor[];\n resetServerContext?: () => void;\n};\n\nconst m: ExpoFontLoaderModule =\n typeof window === 'undefined'\n ? // React server mock\n {\n getLoadedFonts() {\n return [];\n },\n loadAsync() {\n return Promise.resolve();\n },\n }\n : requireNativeModule('ExpoFontLoader');\nexport default m;\n"]} \ No newline at end of file diff --git a/packages/expo-font/build/ExpoFontLoader.web.d.ts.map b/packages/expo-font/build/ExpoFontLoader.web.d.ts.map index 969555fd876600..b1e79dc414ee97 100644 --- a/packages/expo-font/build/ExpoFontLoader.web.d.ts.map +++ b/packages/expo-font/build/ExpoFontLoader.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ExpoFontLoader.web.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAgC,MAAM,kBAAkB,CAAC;AAE3F,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAsE9D,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,oBAAoB,CA0GlD,CAAC;wBAcyB,OAAO,cAAc;AAAhD,wBAAiD;AAejD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CAIzF"} \ No newline at end of file +{"version":3,"file":"ExpoFontLoader.web.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAe,KAAK,YAAY,EAAqC,MAAM,cAAc,CAAC;AAsEjG,QAAA,MAAM,cAAc,EAAE,QAAQ,CAAC,oBAAoB,CA0GlD,CAAC;wBAcyB,OAAO,cAAc;AAAhD,wBAAiD;AAejD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CAIzF"} \ No newline at end of file diff --git a/packages/expo-font/build/ExpoFontLoader.web.js.map b/packages/expo-font/build/ExpoFontLoader.web.js.map index 75857b0d030d54..d6538de74b9d40 100644 --- a/packages/expo-font/build/ExpoFontLoader.web.js.map +++ b/packages/expo-font/build/ExpoFontLoader.web.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpoFontLoader.web.js","sourceRoot":"","sources":["../src/ExpoFontLoader.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAI5C,OAAO,EAAE,WAAW,EAAqB,MAAM,cAAc,CAAC;AAE9D,SAAS,qBAAqB;IAC5B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC;IACrC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAE,UAAU,CAAC,KAAuB,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC;AAID,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAC;IACtC,IAAI,KAAK,EAAE,CAAC;QACV,4BAA4B;QAC5B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;QAElC,MAAM,KAAK,GAAe,EAAE,CAAC;QAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,IAAI,YAAY,eAAe,EAAE,CAAC;gBACpC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,gCAAgC,CACvC,cAAsB,EACtB,OAA2B;IAE3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;QAC/B,OAAO,CACL,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,cAAc;YACxC,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,KAAM,IAAI,CAAC,KAAa,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAC1F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,aAAa,GAA2D,IAAI,GAAG,EAAE,CAAC;AAExF,SAAS,4BAA4B;IACnC,MAAM,OAAO,GAAG,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IAC5D,sDAAsD;IACtD,OAAO;QACL;YACE,GAAG;YACH,EAAE,EAAE,EAAE;YACN,IAAI,EAAE,OAAO;SACd;QACD,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YAC5B,EAAE,EAAE,MAAe;YACnB,WAAW,EAAE,EAAE;YACf,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,SAAkB;YACvB,IAAI,EAAE,MAAe;SACtB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,MAAM,cAAc,GAAmC;IACrD,KAAK,CAAC,cAAc;QAClB,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAE1C,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAC5C,IAAI,OAAO,IAAI,OAAO,YAAY,gBAAgB,EAAE,CAAC;YACnD,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,cAAsB,EAAE,OAA2B;QACnE,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,KAAK,GAAG,gCAAgC,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACxE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,kBAAkB;QAChB,MAAM,QAAQ,GAAG,4BAA4B,EAAE,CAAC;QAEhD,OAAO,QAAQ;aACZ,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YACf,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,OAAO;oBACV,OAAO,cAAc,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,GAAG,UAAU,CAAC;gBAC5D,KAAK,MAAM;oBACT,OAAO,cAAc,OAAO,CAAC,GAAG,WAAW,OAAO,CAAC,IAAI,SAAS,OAAO,CAAC,EAAE,kBAAkB,OAAO,CAAC,WAAW,MAAM,CAAC;gBACxH;oBACE,OAAO,EAAE,CAAC;YACd,CAAC;QACH,CAAC,CAAC;aACD,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAED,4BAA4B;QAC1B,OAAO,4BAA4B,EAAE,CAAC;IACxC,CAAC;IAED,kBAAkB;QAChB,aAAa,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,cAAc;QACZ,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;QACD,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACxD,CAAC;IAED,QAAQ,CAAC,cAAsB,EAAE,WAA8B,EAAE;QAC/D,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBAClD,OAAO,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC;YACvC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,gCAAgC,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IAChF,CAAC;IAED,uHAAuH;IACvH,wFAAwF;IACxF,SAAS,CAAC,cAAsB,EAAE,QAAsB;QACtD,IAAI,OAAO,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC5C,6EAA6E;YAC7E,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,0DAA0D,OAAO,QAAQ,EAAE,CAC5E,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,aAAa,CAAC,GAAG,CAAC;gBAChB,IAAI,EAAE,cAAc;gBACpB,GAAG,EAAE,sBAAsB,CAAC,cAAc,EAAE,QAAQ,CAAC;gBACrD,kCAAkC;gBAClC,UAAU,EAAE,QAAQ,CAAC,GAAI;aAC1B,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,MAAM,cAAc,GAAG,QAAQ,CAAC,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,CAAC,WAAW,KAAK,UAAU,CAAC;QACxF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,UAAU,CAClB,qBAAqB,EACrB,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAElC,MAAM,GAAG,GAAG,gCAAgC,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,eAAe,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,8BAA8B,EAAE,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,OAAO,IAAI,YAAY,CAAC,cAAc,EAAE;YACtC,4FAA4F;YAC5F,OAAO,EAAE,QAAQ,CAAC,OAAO;SAC1B,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;CACF,CAAC;AAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,KAAK,IAAI,OAAO,MAAM,KAAK,WAAW,CAAC;AAEhF,SAAS,oBAAoB;IAC3B,OAAO,cAAc,CAAC;AACxB,CAAC;AACD,MAAM,QAAQ,GAAG,QAAQ;IACvB,CAAC,CAAC,cAAc;IAChB,CAAC,CAAC,gFAAgF;QAChF,4FAA4F;QAC5F,2DAA2D;QAC3D,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC;AAE9D,eAAe,QAAiC,CAAC;AAEjD,MAAM,EAAE,GAAG,sBAAsB,CAAC;AAElC,SAAS,eAAe;IACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAC5C,IAAI,OAAO,IAAI,OAAO,YAAY,gBAAgB,EAAE,CAAC;QACnD,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IAErB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,UAAkB,EAAE,QAAsB;IAC/E,OAAO,2BAA2B,UAAU,cAAc,QAAQ,CAAC,GAAG,mBACpE,QAAQ,CAAC,OAAO,IAAI,WAAW,CAAC,IAClC,GAAG,CAAC;AACN,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB,EAAE,QAAsB;IACjE,MAAM,SAAS,GAAG,sBAAsB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE/D,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,+FAA+F;IAC/F,wDAAwD;IACxD,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;QAC5B,MAAM,cAAc,GAAG,YAAmB,CAAC;QAC3C,cAAc,CAAC,UAAU,CAAC,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO;YACnE,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,OAAO,GAAG,SAAS;YAC/C,CAAC,CAAC,SAAS,CAAC;IAChB,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,YAAY,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,8BAA8B;IACrC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IACvC,2EAA2E;IAC3E,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,gCAAgC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5E,iGAAiG;IACjG,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1C,oBAAoB;IACpB,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3C,OAAO,CAAC,QAAQ,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC;AACjD,CAAC","sourcesContent":["import { CodedError, registerWebModule } from 'expo-modules-core';\nimport FontObserver from 'fontfaceobserver';\n\nimport type { ExpoFontLoaderModule, ServerFontResourceDescriptor } from './ExpoFontLoader';\nimport type { UnloadFontOptions } from './Font';\nimport { FontDisplay, type FontResource } from './Font.types';\n\nfunction getFontFaceStyleSheet(): CSSStyleSheet | null {\n if (typeof window === 'undefined') {\n return null;\n }\n const styleSheet = getStyleElement();\n return styleSheet.sheet ? (styleSheet.sheet as CSSStyleSheet) : null;\n}\n\ntype RuleItem = { rule: CSSFontFaceRule; index: number };\n\nfunction getFontFaceRules(): RuleItem[] {\n const sheet = getFontFaceStyleSheet();\n if (sheet) {\n // @ts-ignore: rule iterator\n const rules = [...sheet.cssRules];\n\n const items: RuleItem[] = [];\n\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i];\n if (rule instanceof CSSFontFaceRule) {\n items.push({ rule, index: i });\n }\n }\n return items;\n }\n return [];\n}\n\nfunction getFontFaceRulesMatchingResource(\n fontFamilyName: string,\n options?: UnloadFontOptions\n): RuleItem[] {\n const rules = getFontFaceRules();\n return rules.filter(({ rule }) => {\n return (\n rule.style.fontFamily === fontFamilyName &&\n (options && options.display ? options.display === (rule.style as any).fontDisplay : true)\n );\n });\n}\n\nconst serverContext: Set<{ name: string; css: string; resourceId: string }> = new Set();\n\nfunction getServerResourceDescriptors(): ServerFontResourceDescriptor[] {\n const entries = [...serverContext.entries()];\n if (!entries.length) {\n return [];\n }\n const css = entries.map(([{ css }]) => css).join('\\n');\n const links = entries.map(([{ resourceId }]) => resourceId);\n // TODO: Maybe return nothing if no fonts were loaded.\n return [\n {\n css,\n id: ID,\n type: 'style',\n },\n ...links.map((resourceId) => ({\n as: 'font' as const,\n crossOrigin: '',\n href: resourceId,\n rel: 'preload' as const,\n type: 'link' as const,\n })),\n ];\n}\n\nconst ExpoFontLoader: Required = {\n async unloadAllAsync(): Promise {\n if (typeof window === 'undefined') return;\n\n const element = document.getElementById(ID);\n if (element && element instanceof HTMLStyleElement) {\n document.removeChild(element);\n }\n },\n\n async unloadAsync(fontFamilyName: string, options?: UnloadFontOptions): Promise {\n const sheet = getFontFaceStyleSheet();\n if (!sheet) return;\n const items = getFontFaceRulesMatchingResource(fontFamilyName, options);\n for (const item of items) {\n sheet.deleteRule(item.index);\n }\n },\n\n getServerResources(): string[] {\n const elements = getServerResourceDescriptors();\n\n return elements\n .map((element) => {\n switch (element.type) {\n case 'style':\n return ``;\n case 'link':\n return ``;\n default:\n return '';\n }\n })\n .filter(Boolean);\n },\n\n getServerResourceDescriptors() {\n return getServerResourceDescriptors();\n },\n\n resetServerContext() {\n serverContext.clear();\n },\n\n getLoadedFonts(): string[] {\n if (typeof window === 'undefined') {\n return [...serverContext.values()].map(({ name }) => name);\n }\n const rules = getFontFaceRules();\n return rules.map(({ rule }) => rule.style.fontFamily);\n },\n\n isLoaded(fontFamilyName: string, resource: UnloadFontOptions = {}): boolean {\n if (typeof window === 'undefined') {\n return !![...serverContext.values()].find((asset) => {\n return asset.name === fontFamilyName;\n });\n }\n return getFontFaceRulesMatchingResource(fontFamilyName, resource)?.length > 0;\n },\n\n // NOTE(vonovak): This is used in RN vector-icons to load fonts dynamically on web. Changing the signature is breaking.\n // NOTE(EvanBacon): No async keyword! This cannot return a promise in Node environments.\n loadAsync(fontFamilyName: string, resource: FontResource): Promise {\n if (__DEV__ && typeof resource !== 'object') {\n // to help devving on web, where loadAsync interface is different from native\n throw new CodedError(\n 'ERR_FONT_SOURCE',\n `Expected font resource of type \\`object\\` instead got: ${typeof resource}`\n );\n }\n if (typeof window === 'undefined') {\n serverContext.add({\n name: fontFamilyName,\n css: _createWebFontTemplate(fontFamilyName, resource),\n // @ts-expect-error: typeof string\n resourceId: resource.uri!,\n });\n return Promise.resolve();\n }\n\n const canInjectStyle = document.head && typeof document.head.appendChild === 'function';\n if (!canInjectStyle) {\n throw new CodedError(\n 'ERR_WEB_ENVIRONMENT',\n `The browser's \\`document.head\\` element doesn't support injecting fonts.`\n );\n }\n\n const style = getStyleElement();\n document.head!.appendChild(style);\n\n const res = getFontFaceRulesMatchingResource(fontFamilyName, resource);\n if (!res.length) {\n _createWebStyle(fontFamilyName, resource);\n }\n\n if (!isFontLoadingListenerSupported()) {\n return Promise.resolve();\n }\n\n return new FontObserver(fontFamilyName, {\n // @ts-expect-error: TODO(@kitten): Typings indicate that the polyfill may not support this?\n display: resource.display,\n }).load(resource.testString ?? null, 12000);\n },\n};\n\nconst isServer = process.env.EXPO_OS === 'web' && typeof window === 'undefined';\n\nfunction createExpoFontLoader() {\n return ExpoFontLoader;\n}\nconst toExport = isServer\n ? ExpoFontLoader\n : // @ts-expect-error: registerWebModule calls `new` on the module implementation.\n // Normally that'd be a class but that doesn't work on server, so we use a function instead.\n // TS doesn't like that but we don't need it to be a class.\n registerWebModule(createExpoFontLoader, 'ExpoFontLoader');\n\nexport default toExport as typeof ExpoFontLoader;\n\nconst ID = 'expo-generated-fonts';\n\nfunction getStyleElement(): HTMLStyleElement {\n const element = document.getElementById(ID);\n if (element && element instanceof HTMLStyleElement) {\n return element;\n }\n const styleElement = document.createElement('style');\n styleElement.id = ID;\n\n return styleElement;\n}\n\nexport function _createWebFontTemplate(fontFamily: string, resource: FontResource): string {\n return `@font-face{font-family:\"${fontFamily}\";src:url(\"${resource.uri}\");font-display:${\n resource.display || FontDisplay.AUTO\n }}`;\n}\n\nfunction _createWebStyle(fontFamily: string, resource: FontResource): HTMLStyleElement {\n const fontStyle = _createWebFontTemplate(fontFamily, resource);\n\n const styleElement = getStyleElement();\n // @ts-ignore: TypeScript does not define HTMLStyleElement::styleSheet. This is just for IE and\n // possibly can be removed if it's unnecessary on IE 11.\n if (styleElement.styleSheet) {\n const styleElementIE = styleElement as any;\n styleElementIE.styleSheet.cssText = styleElementIE.styleSheet.cssText\n ? styleElementIE.styleSheet.cssText + fontStyle\n : fontStyle;\n } else {\n const textNode = document.createTextNode(fontStyle);\n styleElement.appendChild(textNode);\n }\n return styleElement;\n}\n\nfunction isFontLoadingListenerSupported(): boolean {\n const { userAgent } = window.navigator;\n // WebKit is broken https://github.com/bramstein/fontfaceobserver/issues/95\n const isIOS = !!userAgent.match(/iPad|iPhone/i);\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n // Edge is broken https://github.com/bramstein/fontfaceobserver/issues/109#issuecomment-333356795\n const isEdge = userAgent.includes('Edge');\n // Internet Explorer\n const isIE = userAgent.includes('Trident');\n return !isSafari && !isIOS && !isEdge && !isIE;\n}\n"]} \ No newline at end of file +{"version":3,"file":"ExpoFontLoader.web.js","sourceRoot":"","sources":["../src/ExpoFontLoader.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { CodedError, registerWebModule } from 'expo-modules-core';\nimport FontObserver from 'fontfaceobserver';\n\nimport type { ExpoFontLoaderModule } from './ExpoFontLoader';\nimport type { UnloadFontOptions } from './Font';\nimport { FontDisplay, type FontResource, type ServerFontResourceDescriptor } from './Font.types';\n\nfunction getFontFaceStyleSheet(): CSSStyleSheet | null {\n if (typeof window === 'undefined') {\n return null;\n }\n const styleSheet = getStyleElement();\n return styleSheet.sheet ? (styleSheet.sheet as CSSStyleSheet) : null;\n}\n\ntype RuleItem = { rule: CSSFontFaceRule; index: number };\n\nfunction getFontFaceRules(): RuleItem[] {\n const sheet = getFontFaceStyleSheet();\n if (sheet) {\n // @ts-ignore: rule iterator\n const rules = [...sheet.cssRules];\n\n const items: RuleItem[] = [];\n\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i];\n if (rule instanceof CSSFontFaceRule) {\n items.push({ rule, index: i });\n }\n }\n return items;\n }\n return [];\n}\n\nfunction getFontFaceRulesMatchingResource(\n fontFamilyName: string,\n options?: UnloadFontOptions\n): RuleItem[] {\n const rules = getFontFaceRules();\n return rules.filter(({ rule }) => {\n return (\n rule.style.fontFamily === fontFamilyName &&\n (options && options.display ? options.display === (rule.style as any).fontDisplay : true)\n );\n });\n}\n\nconst serverContext: Set<{ name: string; css: string; resourceId: string }> = new Set();\n\nfunction getServerResourceDescriptors(): ServerFontResourceDescriptor[] {\n const entries = [...serverContext.entries()];\n if (!entries.length) {\n return [];\n }\n const css = entries.map(([{ css }]) => css).join('\\n');\n const links = entries.map(([{ resourceId }]) => resourceId);\n // TODO: Maybe return nothing if no fonts were loaded.\n return [\n {\n css,\n id: ID,\n type: 'style' as const,\n },\n ...links.map((resourceId) => ({\n as: 'font' as const,\n crossOrigin: '' as const,\n href: resourceId,\n rel: 'preload' as const,\n type: 'link' as const,\n })),\n ];\n}\n\nconst ExpoFontLoader: Required = {\n async unloadAllAsync(): Promise {\n if (typeof window === 'undefined') return;\n\n const element = document.getElementById(ID);\n if (element && element instanceof HTMLStyleElement) {\n document.removeChild(element);\n }\n },\n\n async unloadAsync(fontFamilyName: string, options?: UnloadFontOptions): Promise {\n const sheet = getFontFaceStyleSheet();\n if (!sheet) return;\n const items = getFontFaceRulesMatchingResource(fontFamilyName, options);\n for (const item of items) {\n sheet.deleteRule(item.index);\n }\n },\n\n getServerResources(): string[] {\n const elements = getServerResourceDescriptors();\n\n return elements\n .map((element) => {\n switch (element.type) {\n case 'style':\n return ``;\n case 'link':\n return ``;\n default:\n return '';\n }\n })\n .filter(Boolean);\n },\n\n getServerResourceDescriptors() {\n return getServerResourceDescriptors();\n },\n\n resetServerContext() {\n serverContext.clear();\n },\n\n getLoadedFonts(): string[] {\n if (typeof window === 'undefined') {\n return [...serverContext.values()].map(({ name }) => name);\n }\n const rules = getFontFaceRules();\n return rules.map(({ rule }) => rule.style.fontFamily);\n },\n\n isLoaded(fontFamilyName: string, resource: UnloadFontOptions = {}): boolean {\n if (typeof window === 'undefined') {\n return !![...serverContext.values()].find((asset) => {\n return asset.name === fontFamilyName;\n });\n }\n return getFontFaceRulesMatchingResource(fontFamilyName, resource)?.length > 0;\n },\n\n // NOTE(vonovak): This is used in RN vector-icons to load fonts dynamically on web. Changing the signature is breaking.\n // NOTE(EvanBacon): No async keyword! This cannot return a promise in Node environments.\n loadAsync(fontFamilyName: string, resource: FontResource): Promise {\n if (__DEV__ && typeof resource !== 'object') {\n // to help devving on web, where loadAsync interface is different from native\n throw new CodedError(\n 'ERR_FONT_SOURCE',\n `Expected font resource of type \\`object\\` instead got: ${typeof resource}`\n );\n }\n if (typeof window === 'undefined') {\n serverContext.add({\n name: fontFamilyName,\n css: _createWebFontTemplate(fontFamilyName, resource),\n // @ts-expect-error: typeof string\n resourceId: resource.uri!,\n });\n return Promise.resolve();\n }\n\n const canInjectStyle = document.head && typeof document.head.appendChild === 'function';\n if (!canInjectStyle) {\n throw new CodedError(\n 'ERR_WEB_ENVIRONMENT',\n `The browser's \\`document.head\\` element doesn't support injecting fonts.`\n );\n }\n\n const style = getStyleElement();\n document.head!.appendChild(style);\n\n const res = getFontFaceRulesMatchingResource(fontFamilyName, resource);\n if (!res.length) {\n _createWebStyle(fontFamilyName, resource);\n }\n\n if (!isFontLoadingListenerSupported()) {\n return Promise.resolve();\n }\n\n return new FontObserver(fontFamilyName, {\n // @ts-expect-error: TODO(@kitten): Typings indicate that the polyfill may not support this?\n display: resource.display,\n }).load(resource.testString ?? null, 12000);\n },\n};\n\nconst isServer = process.env.EXPO_OS === 'web' && typeof window === 'undefined';\n\nfunction createExpoFontLoader() {\n return ExpoFontLoader;\n}\nconst toExport = isServer\n ? ExpoFontLoader\n : // @ts-expect-error: registerWebModule calls `new` on the module implementation.\n // Normally that'd be a class but that doesn't work on server, so we use a function instead.\n // TS doesn't like that but we don't need it to be a class.\n registerWebModule(createExpoFontLoader, 'ExpoFontLoader');\n\nexport default toExport as typeof ExpoFontLoader;\n\nconst ID = 'expo-generated-fonts';\n\nfunction getStyleElement(): HTMLStyleElement {\n const element = document.getElementById(ID);\n if (element && element instanceof HTMLStyleElement) {\n return element;\n }\n const styleElement = document.createElement('style');\n styleElement.id = ID;\n\n return styleElement;\n}\n\nexport function _createWebFontTemplate(fontFamily: string, resource: FontResource): string {\n return `@font-face{font-family:\"${fontFamily}\";src:url(\"${resource.uri}\");font-display:${\n resource.display || FontDisplay.AUTO\n }}`;\n}\n\nfunction _createWebStyle(fontFamily: string, resource: FontResource): HTMLStyleElement {\n const fontStyle = _createWebFontTemplate(fontFamily, resource);\n\n const styleElement = getStyleElement();\n // @ts-ignore: TypeScript does not define HTMLStyleElement::styleSheet. This is just for IE and\n // possibly can be removed if it's unnecessary on IE 11.\n if (styleElement.styleSheet) {\n const styleElementIE = styleElement as any;\n styleElementIE.styleSheet.cssText = styleElementIE.styleSheet.cssText\n ? styleElementIE.styleSheet.cssText + fontStyle\n : fontStyle;\n } else {\n const textNode = document.createTextNode(fontStyle);\n styleElement.appendChild(textNode);\n }\n return styleElement;\n}\n\nfunction isFontLoadingListenerSupported(): boolean {\n const { userAgent } = window.navigator;\n // WebKit is broken https://github.com/bramstein/fontfaceobserver/issues/95\n const isIOS = !!userAgent.match(/iPad|iPhone/i);\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n // Edge is broken https://github.com/bramstein/fontfaceobserver/issues/109#issuecomment-333356795\n const isEdge = userAgent.includes('Edge');\n // Internet Explorer\n const isIE = userAgent.includes('Trident');\n return !isSafari && !isIOS && !isEdge && !isIE;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-font/build/Font.d.ts b/packages/expo-font/build/Font.d.ts index d38d6f3a6b2a00..e4c396453447bd 100644 --- a/packages/expo-font/build/Font.d.ts +++ b/packages/expo-font/build/Font.d.ts @@ -50,5 +50,5 @@ export declare function unloadAllAsync(): Promise; * @hidden */ export declare function unloadAsync(fontFamilyOrFontMap: string | Record, options?: UnloadFontOptions): Promise; -export { FontDisplay, type FontSource, type FontResource, type UnloadFontOptions, } from './Font.types'; +export { FontDisplay, type FontSource, type FontResource, type UnloadFontOptions, type ServerFontResourceDescriptor, } from './Font.types'; //# sourceMappingURL=Font.d.ts.map \ No newline at end of file diff --git a/packages/expo-font/build/Font.d.ts.map b/packages/expo-font/build/Font.d.ts.map index fcf5a5ea3fb408..795482006bf9c4 100644 --- a/packages/expo-font/build/Font.d.ts.map +++ b/packages/expo-font/build/Font.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Font.d.ts","sourceRoot":"","sources":["../src/Font.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAalE;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAUpD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAErD;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACxD,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAkCf;AA0CD;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAapD;AAGD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC/D,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkBf;AA0BD,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC"} \ No newline at end of file +{"version":3,"file":"Font.d.ts","sourceRoot":"","sources":["../src/Font.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAalE;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAUpD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAErD;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACxD,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAkCf;AA0CD;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAapD;AAGD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC/D,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkBf;AA0BD,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,GAClC,MAAM,cAAc,CAAC"} \ No newline at end of file diff --git a/packages/expo-font/build/Font.js.map b/packages/expo-font/build/Font.js.map index a6b1c075168048..838d0a33dff96e 100644 --- a/packages/expo-font/build/Font.js.map +++ b/packages/expo-font/build/Font.js.map @@ -1 +1 @@ -{"version":3,"file":"Font.js","sourceRoot":"","sources":["../src/Font.ts"],"names":[],"mappings":"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","sourcesContent":["import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport type { FontSource, UnloadFontOptions } from './Font.types';\nimport { getAssetForSource, loadSingleFontAsync } from './FontLoader';\nimport {\n isLoadedInCache,\n isLoadedNative,\n loadPromises,\n markLoaded,\n purgeCache,\n purgeFontFamilyFromCache,\n} from './memory';\nimport { registerStaticFont } from './server';\n\n// @needsAudit\n/**\n * Synchronously detect if the font for `fontFamily` has finished loading.\n *\n * @param fontFamily The name used to load the `FontResource`.\n * @return Returns `true` if the font has fully loaded.\n */\nexport function isLoaded(fontFamily: string): boolean {\n if (Platform.OS === 'web') {\n if (typeof ExpoFontLoader.isLoaded !== 'function') {\n throw new Error(\n `expected ExpoFontLoader.isLoaded to be a function, was ${typeof ExpoFontLoader.isLoaded}`\n );\n }\n return isLoadedInCache(fontFamily) || ExpoFontLoader.isLoaded(fontFamily);\n }\n return isLoadedNative(fontFamily);\n}\n\n/**\n * Synchronously get all the fonts that have been loaded.\n * This includes fonts that were bundled at build time using the config plugin, as well as those loaded at runtime using `loadAsync`.\n *\n * @returns Returns array of strings which you can use as `fontFamily` [style prop](https://reactnative.dev/docs/text#style).\n */\nexport function getLoadedFonts(): string[] {\n return ExpoFontLoader.getLoadedFonts();\n}\n\n// @needsAudit\n/**\n * Synchronously detect if the font for `fontFamily` is still being loaded.\n *\n * @param fontFamily The name used to load the `FontResource`.\n * @returns Returns `true` if the font is still loading.\n */\nexport function isLoading(fontFamily: string): boolean {\n return fontFamily in loadPromises;\n}\n\n// @needsAudit\n/**\n * An efficient method for loading fonts from static or remote resources which can then be used\n * with the platform's native text elements. In the browser, this generates a `@font-face` block in\n * a shared style sheet for fonts. No CSS is needed to use this method.\n *\n * > **Note**: We recommend using the [config plugin](#configuration-in-app-config) instead whenever possible.\n *\n * @param fontFamilyOrFontMap String or map of values that can be used as the `fontFamily` [style prop](https://reactnative.dev/docs/text#style)\n * with React Native `Text` elements.\n * @param source The font asset that should be loaded into the `fontFamily` namespace.\n *\n * @return Returns a promise that fulfils when the font has loaded. Often you may want to wrap the\n * method in a `try/catch/finally` to ensure the app continues if the font fails to load.\n */\nexport function loadAsync(\n fontFamilyOrFontMap: string | Record,\n source?: FontSource\n): Promise {\n // NOTE(EvanBacon): Static render pass on web must be synchronous to collect all fonts.\n // Because of this, `loadAsync` doesn't use the `async` keyword and deviates from the\n // standard Expo SDK style guide.\n const isServer = Platform.OS === 'web' && typeof window === 'undefined';\n\n if (typeof fontFamilyOrFontMap === 'object') {\n if (source) {\n return Promise.reject(\n new CodedError(\n `ERR_FONT_API`,\n `No fontFamily can be used for the provided source: ${source}. The second argument of \\`loadAsync()\\` can only be used with a \\`string\\` value as the first argument.`\n )\n );\n }\n const fontMap = fontFamilyOrFontMap;\n const names = Object.keys(fontMap);\n\n if (isServer) {\n names.map((name) => registerStaticFont(name, fontMap[name]));\n return Promise.resolve();\n }\n\n return Promise.all(names.map((name) => loadFontInNamespaceAsync(name, fontMap[name]))).then(\n () => {}\n );\n }\n\n if (isServer) {\n registerStaticFont(fontFamilyOrFontMap, source);\n return Promise.resolve();\n }\n\n return loadFontInNamespaceAsync(fontFamilyOrFontMap, source);\n}\n\nasync function loadFontInNamespaceAsync(\n fontFamily: string,\n source?: FontSource | null\n): Promise {\n if (!source) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Cannot load null or undefined font source: { \"${fontFamily}\": ${source} }. Expected asset of type \\`FontSource\\` for fontFamily of name: \"${fontFamily}\"`\n );\n }\n\n // we consult the native module to see if the font is already loaded\n // this is slower than checking the cache but can help avoid loading the same font n times\n if (isLoaded(fontFamily)) {\n return;\n }\n\n if (loadPromises.hasOwnProperty(fontFamily)) {\n return loadPromises[fontFamily];\n }\n\n // Important: we want all callers that concurrently try to load the same font to await the same\n // promise. If we're here, we haven't created the promise yet. To ensure we create only one\n // promise in the program, we need to create the promise synchronously without yielding the event\n // loop from this point.\n\n const asset = getAssetForSource(source);\n loadPromises[fontFamily] = (async () => {\n try {\n await loadSingleFontAsync(fontFamily, asset);\n markLoaded(fontFamily);\n } finally {\n delete loadPromises[fontFamily];\n }\n })();\n\n await loadPromises[fontFamily];\n}\n\n// @needsAudit\n/**\n * Unloads all the custom fonts. This is used for testing.\n * @hidden\n */\nexport async function unloadAllAsync(): Promise {\n if (!ExpoFontLoader.unloadAllAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAllAsync');\n }\n\n if (Object.keys(loadPromises).length) {\n throw new CodedError(\n `ERR_UNLOAD`,\n `Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`\n );\n }\n purgeCache();\n await ExpoFontLoader.unloadAllAsync();\n}\n\n// @needsAudit\n/**\n * Unload custom fonts matching the `fontFamily`s and display values provided.\n * This is used for testing.\n *\n * @param fontFamilyOrFontMap The name or names of the custom fonts that will be unloaded.\n * @param options When `fontFamilyOrFontMap` is a string, this should be the font source used to load\n * the custom font originally.\n * @hidden\n */\nexport async function unloadAsync(\n fontFamilyOrFontMap: string | Record,\n options?: UnloadFontOptions\n): Promise {\n if (!ExpoFontLoader.unloadAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAsync');\n }\n if (typeof fontFamilyOrFontMap === 'object') {\n if (options) {\n throw new CodedError(\n `ERR_FONT_API`,\n `No fontFamily can be used for the provided options: ${options}. The second argument of \\`unloadAsync()\\` can only be used with a \\`string\\` value as the first argument.`\n );\n }\n const fontMap = fontFamilyOrFontMap;\n const names = Object.keys(fontMap);\n await Promise.all(names.map((name) => unloadFontInNamespaceAsync(name, fontMap[name])));\n return;\n }\n\n return await unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);\n}\n\nasync function unloadFontInNamespaceAsync(\n fontFamily: string,\n options?: UnloadFontOptions\n): Promise {\n if (!isLoaded(fontFamily)) {\n return;\n } else {\n purgeFontFamilyFromCache(fontFamily);\n }\n\n // Important: we want all callers that concurrently try to load the same font to await the same\n // promise. If we're here, we haven't created the promise yet. To ensure we create only one\n // promise in the program, we need to create the promise synchronously without yielding the event\n // loop from this point.\n\n if (!fontFamily) {\n throw new CodedError(`ERR_FONT_FAMILY`, `Cannot unload an empty name`);\n }\n if (!ExpoFontLoader.unloadAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAsync');\n }\n await ExpoFontLoader.unloadAsync(fontFamily, options);\n}\n\nexport {\n FontDisplay,\n type FontSource,\n type FontResource,\n type UnloadFontOptions,\n} from './Font.types';\n"]} \ No newline at end of file +{"version":3,"file":"Font.js","sourceRoot":"","sources":["../src/Font.ts"],"names":[],"mappings":"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","sourcesContent":["import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport type { FontSource, UnloadFontOptions } from './Font.types';\nimport { getAssetForSource, loadSingleFontAsync } from './FontLoader';\nimport {\n isLoadedInCache,\n isLoadedNative,\n loadPromises,\n markLoaded,\n purgeCache,\n purgeFontFamilyFromCache,\n} from './memory';\nimport { registerStaticFont } from './server';\n\n// @needsAudit\n/**\n * Synchronously detect if the font for `fontFamily` has finished loading.\n *\n * @param fontFamily The name used to load the `FontResource`.\n * @return Returns `true` if the font has fully loaded.\n */\nexport function isLoaded(fontFamily: string): boolean {\n if (Platform.OS === 'web') {\n if (typeof ExpoFontLoader.isLoaded !== 'function') {\n throw new Error(\n `expected ExpoFontLoader.isLoaded to be a function, was ${typeof ExpoFontLoader.isLoaded}`\n );\n }\n return isLoadedInCache(fontFamily) || ExpoFontLoader.isLoaded(fontFamily);\n }\n return isLoadedNative(fontFamily);\n}\n\n/**\n * Synchronously get all the fonts that have been loaded.\n * This includes fonts that were bundled at build time using the config plugin, as well as those loaded at runtime using `loadAsync`.\n *\n * @returns Returns array of strings which you can use as `fontFamily` [style prop](https://reactnative.dev/docs/text#style).\n */\nexport function getLoadedFonts(): string[] {\n return ExpoFontLoader.getLoadedFonts();\n}\n\n// @needsAudit\n/**\n * Synchronously detect if the font for `fontFamily` is still being loaded.\n *\n * @param fontFamily The name used to load the `FontResource`.\n * @returns Returns `true` if the font is still loading.\n */\nexport function isLoading(fontFamily: string): boolean {\n return fontFamily in loadPromises;\n}\n\n// @needsAudit\n/**\n * An efficient method for loading fonts from static or remote resources which can then be used\n * with the platform's native text elements. In the browser, this generates a `@font-face` block in\n * a shared style sheet for fonts. No CSS is needed to use this method.\n *\n * > **Note**: We recommend using the [config plugin](#configuration-in-app-config) instead whenever possible.\n *\n * @param fontFamilyOrFontMap String or map of values that can be used as the `fontFamily` [style prop](https://reactnative.dev/docs/text#style)\n * with React Native `Text` elements.\n * @param source The font asset that should be loaded into the `fontFamily` namespace.\n *\n * @return Returns a promise that fulfils when the font has loaded. Often you may want to wrap the\n * method in a `try/catch/finally` to ensure the app continues if the font fails to load.\n */\nexport function loadAsync(\n fontFamilyOrFontMap: string | Record,\n source?: FontSource\n): Promise {\n // NOTE(EvanBacon): Static render pass on web must be synchronous to collect all fonts.\n // Because of this, `loadAsync` doesn't use the `async` keyword and deviates from the\n // standard Expo SDK style guide.\n const isServer = Platform.OS === 'web' && typeof window === 'undefined';\n\n if (typeof fontFamilyOrFontMap === 'object') {\n if (source) {\n return Promise.reject(\n new CodedError(\n `ERR_FONT_API`,\n `No fontFamily can be used for the provided source: ${source}. The second argument of \\`loadAsync()\\` can only be used with a \\`string\\` value as the first argument.`\n )\n );\n }\n const fontMap = fontFamilyOrFontMap;\n const names = Object.keys(fontMap);\n\n if (isServer) {\n names.map((name) => registerStaticFont(name, fontMap[name]));\n return Promise.resolve();\n }\n\n return Promise.all(names.map((name) => loadFontInNamespaceAsync(name, fontMap[name]))).then(\n () => {}\n );\n }\n\n if (isServer) {\n registerStaticFont(fontFamilyOrFontMap, source);\n return Promise.resolve();\n }\n\n return loadFontInNamespaceAsync(fontFamilyOrFontMap, source);\n}\n\nasync function loadFontInNamespaceAsync(\n fontFamily: string,\n source?: FontSource | null\n): Promise {\n if (!source) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Cannot load null or undefined font source: { \"${fontFamily}\": ${source} }. Expected asset of type \\`FontSource\\` for fontFamily of name: \"${fontFamily}\"`\n );\n }\n\n // we consult the native module to see if the font is already loaded\n // this is slower than checking the cache but can help avoid loading the same font n times\n if (isLoaded(fontFamily)) {\n return;\n }\n\n if (loadPromises.hasOwnProperty(fontFamily)) {\n return loadPromises[fontFamily];\n }\n\n // Important: we want all callers that concurrently try to load the same font to await the same\n // promise. If we're here, we haven't created the promise yet. To ensure we create only one\n // promise in the program, we need to create the promise synchronously without yielding the event\n // loop from this point.\n\n const asset = getAssetForSource(source);\n loadPromises[fontFamily] = (async () => {\n try {\n await loadSingleFontAsync(fontFamily, asset);\n markLoaded(fontFamily);\n } finally {\n delete loadPromises[fontFamily];\n }\n })();\n\n await loadPromises[fontFamily];\n}\n\n// @needsAudit\n/**\n * Unloads all the custom fonts. This is used for testing.\n * @hidden\n */\nexport async function unloadAllAsync(): Promise {\n if (!ExpoFontLoader.unloadAllAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAllAsync');\n }\n\n if (Object.keys(loadPromises).length) {\n throw new CodedError(\n `ERR_UNLOAD`,\n `Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`\n );\n }\n purgeCache();\n await ExpoFontLoader.unloadAllAsync();\n}\n\n// @needsAudit\n/**\n * Unload custom fonts matching the `fontFamily`s and display values provided.\n * This is used for testing.\n *\n * @param fontFamilyOrFontMap The name or names of the custom fonts that will be unloaded.\n * @param options When `fontFamilyOrFontMap` is a string, this should be the font source used to load\n * the custom font originally.\n * @hidden\n */\nexport async function unloadAsync(\n fontFamilyOrFontMap: string | Record,\n options?: UnloadFontOptions\n): Promise {\n if (!ExpoFontLoader.unloadAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAsync');\n }\n if (typeof fontFamilyOrFontMap === 'object') {\n if (options) {\n throw new CodedError(\n `ERR_FONT_API`,\n `No fontFamily can be used for the provided options: ${options}. The second argument of \\`unloadAsync()\\` can only be used with a \\`string\\` value as the first argument.`\n );\n }\n const fontMap = fontFamilyOrFontMap;\n const names = Object.keys(fontMap);\n await Promise.all(names.map((name) => unloadFontInNamespaceAsync(name, fontMap[name])));\n return;\n }\n\n return await unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);\n}\n\nasync function unloadFontInNamespaceAsync(\n fontFamily: string,\n options?: UnloadFontOptions\n): Promise {\n if (!isLoaded(fontFamily)) {\n return;\n } else {\n purgeFontFamilyFromCache(fontFamily);\n }\n\n // Important: we want all callers that concurrently try to load the same font to await the same\n // promise. If we're here, we haven't created the promise yet. To ensure we create only one\n // promise in the program, we need to create the promise synchronously without yielding the event\n // loop from this point.\n\n if (!fontFamily) {\n throw new CodedError(`ERR_FONT_FAMILY`, `Cannot unload an empty name`);\n }\n if (!ExpoFontLoader.unloadAsync) {\n throw new UnavailabilityError('expo-font', 'unloadAsync');\n }\n await ExpoFontLoader.unloadAsync(fontFamily, options);\n}\n\nexport {\n FontDisplay,\n type FontSource,\n type FontResource,\n type UnloadFontOptions,\n type ServerFontResourceDescriptor,\n} from './Font.types';\n"]} \ No newline at end of file diff --git a/packages/expo-font/build/Font.types.d.ts b/packages/expo-font/build/Font.types.d.ts index 01066ed0723dfb..5091cc51fe5481 100644 --- a/packages/expo-font/build/Font.types.d.ts +++ b/packages/expo-font/build/Font.types.d.ts @@ -69,4 +69,15 @@ export declare enum FontDisplay { */ export type UnloadFontOptions = Pick; export type UseFontHook = (map: string | Record) => [boolean, Error | null]; +export type ServerFontResourceDescriptor = { + type: 'style'; + css: string; + id: string; +} | { + type: 'link'; + as: 'font'; + crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined; + href: string; + rel: 'preload'; +}; //# sourceMappingURL=Font.types.d.ts.map \ No newline at end of file diff --git a/packages/expo-font/build/Font.types.d.ts.map b/packages/expo-font/build/Font.types.d.ts.map index fc567f3d5c8bca..af97781c50d7fb 100644 --- a/packages/expo-font/build/Font.types.d.ts.map +++ b/packages/expo-font/build/Font.types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Font.types.d.ts","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;AAGhE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAGF;;;;;;;;;GASG;AACH,oBAAY,WAAW;IACrB;;;;OAIG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,KAAK,UAAU;IACf;;;;;;OAMG;IACH,QAAQ,aAAa;IACrB;;;OAGG;IACH,QAAQ,aAAa;CACtB;AAGD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC"} \ No newline at end of file +{"version":3,"file":"Font.types.d.ts","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;AAGhE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAGF;;;;;;;;;GASG;AACH,oBAAY,WAAW;IACrB;;;;OAIG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,KAAK,UAAU;IACf;;;;;;OAMG;IACH,QAAQ,aAAa;IACrB;;;OAGG;IACH,QAAQ,aAAa;CACtB;AAGD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;AAEhG,MAAM,MAAM,4BAA4B,GACpC;IACE,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,GAAG,EAAE,GAAG,SAAS,CAAC;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,SAAS,CAAC;CAChB,CAAC"} \ No newline at end of file diff --git a/packages/expo-font/build/Font.types.js.map b/packages/expo-font/build/Font.types.js.map index 53a3c05f91175f..1cec851c79d944 100644 --- a/packages/expo-font/build/Font.types.js.map +++ b/packages/expo-font/build/Font.types.js.map @@ -1 +1 @@ -{"version":3,"file":"Font.types.js","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AA6BA,cAAc;AACd;;;;;;;;;GASG;AACH,MAAM,CAAN,IAAY,WA8BX;AA9BD,WAAY,WAAW;IACrB;;;;OAIG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,8BAAe,CAAA;IACf;;;;;;OAMG;IACH,oCAAqB,CAAA;IACrB;;;OAGG;IACH,oCAAqB,CAAA;AACvB,CAAC,EA9BW,WAAW,KAAX,WAAW,QA8BtB","sourcesContent":["import type { Asset } from 'expo-asset';\n\n// @needsAudit\n/**\n * The different types of assets you can provide to the [`loadAsync()`](#loadasyncfontfamilyorfontmap-source) function.\n * A font source can be a URI, a module ID, or an Expo Asset.\n */\nexport type FontSource = string | number | Asset | FontResource;\n\n// @needsAudit\n/**\n * An object used to dictate the resource that is loaded into the provided font namespace when used\n * with [`loadAsync`](#loadasyncfontfamilyorfontmap-source).\n */\nexport type FontResource = {\n uri?: string | number;\n /**\n * Sets the [`font-display`](#fontdisplay) property for a given typeface in the browser.\n * @platform web\n */\n display?: FontDisplay;\n default?: string;\n /**\n * Sets a custom test string passed to the [FontFace Observer](https://www.npmjs.com/package/fontfaceobserver).\n * @platform web\n */\n testString?: string;\n};\n\n// @needsAudit\n/**\n * Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)\n * for a given typeface. The default font value on web is `FontDisplay.AUTO`.\n * Even though setting the `fontDisplay` does nothing on native platforms, the default behavior\n * emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default\n * functionality varies on One Plus devices. In the browser this value is set in the generated\n * `@font-face` CSS block and not as a style property meaning you cannot dynamically change this\n * value based on the element it's used in.\n * @platform web\n */\nexport enum FontDisplay {\n /**\n * __(Default)__ The font display strategy is defined by the user agent or platform.\n * This generally defaults to the text being invisible until the font is loaded.\n * Good for buttons or banners that require a specific treatment.\n */\n AUTO = 'auto',\n /**\n * Fallback text is rendered immediately with a default font while the desired font is loaded.\n * This is good for making the content appear to load instantly and is usually preferred.\n */\n SWAP = 'swap',\n /**\n * The text will be invisible until the font has loaded. If the font fails to load then nothing\n * will appear - it's best to turn this off when debugging missing text.\n */\n BLOCK = 'block',\n /**\n * Splits the behavior between `SWAP` and `BLOCK`.\n * There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)\n * where the text with a custom font is invisible, after that the text will either swap to the\n * styled text or it'll show the unstyled text and continue to load the custom font. This is good\n * for buttons that need a custom font but should also be quickly available to screen-readers.\n */\n FALLBACK = 'fallback',\n /**\n * This works almost identically to `FALLBACK`, the only difference is that the browser will\n * decide to load the font based on slow connection speed or critical resource demand.\n */\n OPTIONAL = 'optional',\n}\n\n// @needsAudit\n/**\n * Object used to query fonts for unloading.\n * @hidden\n */\nexport type UnloadFontOptions = Pick;\n\nexport type UseFontHook = (map: string | Record) => [boolean, Error | null];\n"]} \ No newline at end of file +{"version":3,"file":"Font.types.js","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AA6BA,cAAc;AACd;;;;;;;;;GASG;AACH,MAAM,CAAN,IAAY,WA8BX;AA9BD,WAAY,WAAW;IACrB;;;;OAIG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,8BAAe,CAAA;IACf;;;;;;OAMG;IACH,oCAAqB,CAAA;IACrB;;;OAGG;IACH,oCAAqB,CAAA;AACvB,CAAC,EA9BW,WAAW,KAAX,WAAW,QA8BtB","sourcesContent":["import type { Asset } from 'expo-asset';\n\n// @needsAudit\n/**\n * The different types of assets you can provide to the [`loadAsync()`](#loadasyncfontfamilyorfontmap-source) function.\n * A font source can be a URI, a module ID, or an Expo Asset.\n */\nexport type FontSource = string | number | Asset | FontResource;\n\n// @needsAudit\n/**\n * An object used to dictate the resource that is loaded into the provided font namespace when used\n * with [`loadAsync`](#loadasyncfontfamilyorfontmap-source).\n */\nexport type FontResource = {\n uri?: string | number;\n /**\n * Sets the [`font-display`](#fontdisplay) property for a given typeface in the browser.\n * @platform web\n */\n display?: FontDisplay;\n default?: string;\n /**\n * Sets a custom test string passed to the [FontFace Observer](https://www.npmjs.com/package/fontfaceobserver).\n * @platform web\n */\n testString?: string;\n};\n\n// @needsAudit\n/**\n * Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)\n * for a given typeface. The default font value on web is `FontDisplay.AUTO`.\n * Even though setting the `fontDisplay` does nothing on native platforms, the default behavior\n * emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default\n * functionality varies on One Plus devices. In the browser this value is set in the generated\n * `@font-face` CSS block and not as a style property meaning you cannot dynamically change this\n * value based on the element it's used in.\n * @platform web\n */\nexport enum FontDisplay {\n /**\n * __(Default)__ The font display strategy is defined by the user agent or platform.\n * This generally defaults to the text being invisible until the font is loaded.\n * Good for buttons or banners that require a specific treatment.\n */\n AUTO = 'auto',\n /**\n * Fallback text is rendered immediately with a default font while the desired font is loaded.\n * This is good for making the content appear to load instantly and is usually preferred.\n */\n SWAP = 'swap',\n /**\n * The text will be invisible until the font has loaded. If the font fails to load then nothing\n * will appear - it's best to turn this off when debugging missing text.\n */\n BLOCK = 'block',\n /**\n * Splits the behavior between `SWAP` and `BLOCK`.\n * There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)\n * where the text with a custom font is invisible, after that the text will either swap to the\n * styled text or it'll show the unstyled text and continue to load the custom font. This is good\n * for buttons that need a custom font but should also be quickly available to screen-readers.\n */\n FALLBACK = 'fallback',\n /**\n * This works almost identically to `FALLBACK`, the only difference is that the browser will\n * decide to load the font based on slow connection speed or critical resource demand.\n */\n OPTIONAL = 'optional',\n}\n\n// @needsAudit\n/**\n * Object used to query fonts for unloading.\n * @hidden\n */\nexport type UnloadFontOptions = Pick;\n\nexport type UseFontHook = (map: string | Record) => [boolean, Error | null];\n\nexport type ServerFontResourceDescriptor =\n | {\n type: 'style';\n css: string;\n id: string;\n }\n | {\n type: 'link';\n as: 'font';\n crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined;\n href: string;\n rel: 'preload';\n };\n"]} \ No newline at end of file diff --git a/packages/expo-font/build/server.d.ts b/packages/expo-font/build/server.d.ts index 749d5fa2e653f3..f3f82b321922a8 100644 --- a/packages/expo-font/build/server.d.ts +++ b/packages/expo-font/build/server.d.ts @@ -1,5 +1,4 @@ -import { type ServerFontResourceDescriptor } from './ExpoFontLoader'; -import type { FontSource } from './Font.types'; +import type { FontSource, ServerFontResourceDescriptor } from './Font.types'; /** * @returns the server resources that should be statically extracted. * @private diff --git a/packages/expo-font/build/server.d.ts.map b/packages/expo-font/build/server.d.ts.map index 94d741e6e926b7..c6bf36b29e8780 100644 --- a/packages/expo-font/build/server.d.ts.map +++ b/packages/expo-font/build/server.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAuB,EAAE,KAAK,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAK7C;AAED,wBAAgB,4BAA4B,IAAI,4BAA4B,EAAE,CAK7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,SAKjC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,QAWhF"} \ No newline at end of file +{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AAG7E;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAK7C;AAED,wBAAgB,4BAA4B,IAAI,4BAA4B,EAAE,CAK7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,SAKjC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,QAWhF"} \ No newline at end of file diff --git a/packages/expo-font/build/server.js b/packages/expo-font/build/server.js index 7179e494f1b151..f098fc3f1f3ae0 100644 --- a/packages/expo-font/build/server.js +++ b/packages/expo-font/build/server.js @@ -1,5 +1,5 @@ import { CodedError, UnavailabilityError } from 'expo-modules-core'; -import ExpoFontLoader, {} from './ExpoFontLoader'; +import ExpoFontLoader from './ExpoFontLoader'; import { getAssetForSource, loadSingleFontAsync } from './FontLoader'; /** * @returns the server resources that should be statically extracted. diff --git a/packages/expo-font/build/server.js.map b/packages/expo-font/build/server.js.map index 73ccb88e90c34d..1fa29eb3c45942 100644 --- a/packages/expo-font/build/server.js.map +++ b/packages/expo-font/build/server.js.map @@ -1 +1 @@ -{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,cAAc,EAAE,EAAqC,MAAM,kBAAkB,CAAC;AAErF,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,8BAA8B,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,cAAc,CAAC,4BAA4B,EAAE,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,MAA0B;IAC/E,2BAA2B;IAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,iDAAiD,UAAU,MAAM,MAAM,sEAAsE,UAAU,GAAG,CAC3J,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAExC,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { CodedError, UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoFontLoader, { type ServerFontResourceDescriptor } from './ExpoFontLoader';\nimport type { FontSource } from './Font.types';\nimport { getAssetForSource, loadSingleFontAsync } from './FontLoader';\n\n/**\n * @returns the server resources that should be statically extracted.\n * @private\n */\nexport function getServerResources(): string[] {\n if (!ExpoFontLoader.getServerResources) {\n throw new UnavailabilityError('expo-font', 'getServerResources');\n }\n return ExpoFontLoader.getServerResources();\n}\n\nexport function getServerResourceDescriptors(): ServerFontResourceDescriptor[] {\n if (!ExpoFontLoader.getServerResourceDescriptors) {\n throw new UnavailabilityError('expo-font', 'getServerResourceDescriptors');\n }\n return ExpoFontLoader.getServerResourceDescriptors();\n}\n\n/**\n * @returns clear the server resources from the global scope.\n * @private\n */\nexport function resetServerContext() {\n if (!ExpoFontLoader.resetServerContext) {\n throw new UnavailabilityError('expo-font', 'resetServerContext');\n }\n return ExpoFontLoader.resetServerContext();\n}\n\nexport function registerStaticFont(fontFamily: string, source?: FontSource | null) {\n // MUST BE A SYNC FUNCTION!\n if (!source) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Cannot load null or undefined font source: { \"${fontFamily}\": ${source} }. Expected asset of type \\`FontSource\\` for fontFamily of name: \"${fontFamily}\"`\n );\n }\n const asset = getAssetForSource(source);\n\n loadSingleFontAsync(fontFamily, asset);\n}\n"]} \ No newline at end of file +{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,8BAA8B,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,cAAc,CAAC,4BAA4B,EAAE,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,MAA0B;IAC/E,2BAA2B;IAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,iDAAiD,UAAU,MAAM,MAAM,sEAAsE,UAAU,GAAG,CAC3J,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAExC,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { CodedError, UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport type { FontSource, ServerFontResourceDescriptor } from './Font.types';\nimport { getAssetForSource, loadSingleFontAsync } from './FontLoader';\n\n/**\n * @returns the server resources that should be statically extracted.\n * @private\n */\nexport function getServerResources(): string[] {\n if (!ExpoFontLoader.getServerResources) {\n throw new UnavailabilityError('expo-font', 'getServerResources');\n }\n return ExpoFontLoader.getServerResources();\n}\n\nexport function getServerResourceDescriptors(): ServerFontResourceDescriptor[] {\n if (!ExpoFontLoader.getServerResourceDescriptors) {\n throw new UnavailabilityError('expo-font', 'getServerResourceDescriptors');\n }\n return ExpoFontLoader.getServerResourceDescriptors();\n}\n\n/**\n * @returns clear the server resources from the global scope.\n * @private\n */\nexport function resetServerContext() {\n if (!ExpoFontLoader.resetServerContext) {\n throw new UnavailabilityError('expo-font', 'resetServerContext');\n }\n return ExpoFontLoader.resetServerContext();\n}\n\nexport function registerStaticFont(fontFamily: string, source?: FontSource | null) {\n // MUST BE A SYNC FUNCTION!\n if (!source) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Cannot load null or undefined font source: { \"${fontFamily}\": ${source} }. Expected asset of type \\`FontSource\\` for fontFamily of name: \"${fontFamily}\"`\n );\n }\n const asset = getAssetForSource(source);\n\n loadSingleFontAsync(fontFamily, asset);\n}\n"]} \ No newline at end of file diff --git a/packages/expo-font/src/ExpoFontLoader.ts b/packages/expo-font/src/ExpoFontLoader.ts index a5b02ef0e75844..c76fffbf9952cf 100644 --- a/packages/expo-font/src/ExpoFontLoader.ts +++ b/packages/expo-font/src/ExpoFontLoader.ts @@ -1,20 +1,6 @@ import { requireNativeModule } from 'expo-modules-core'; -import type { UnloadFontOptions } from './Font.types'; - -export type ServerFontResourceDescriptor = - | { - type: 'style'; - css: string; - id: string; - } - | { - type: 'link'; - as: 'font'; - crossOrigin?: string; - href: string; - rel: 'preload'; - }; +import type { ServerFontResourceDescriptor, UnloadFontOptions } from './Font.types'; export type ExpoFontLoaderModule = { getLoadedFonts: () => string[]; diff --git a/packages/expo-font/src/ExpoFontLoader.web.ts b/packages/expo-font/src/ExpoFontLoader.web.ts index e35f29755c9dd9..625ad9fa6f74de 100644 --- a/packages/expo-font/src/ExpoFontLoader.web.ts +++ b/packages/expo-font/src/ExpoFontLoader.web.ts @@ -1,9 +1,9 @@ import { CodedError, registerWebModule } from 'expo-modules-core'; import FontObserver from 'fontfaceobserver'; -import type { ExpoFontLoaderModule, ServerFontResourceDescriptor } from './ExpoFontLoader'; +import type { ExpoFontLoaderModule } from './ExpoFontLoader'; import type { UnloadFontOptions } from './Font'; -import { FontDisplay, type FontResource } from './Font.types'; +import { FontDisplay, type FontResource, type ServerFontResourceDescriptor } from './Font.types'; function getFontFaceStyleSheet(): CSSStyleSheet | null { if (typeof window === 'undefined') { @@ -61,11 +61,11 @@ function getServerResourceDescriptors(): ServerFontResourceDescriptor[] { { css, id: ID, - type: 'style', + type: 'style' as const, }, ...links.map((resourceId) => ({ as: 'font' as const, - crossOrigin: '', + crossOrigin: '' as const, href: resourceId, rel: 'preload' as const, type: 'link' as const, diff --git a/packages/expo-font/src/Font.ts b/packages/expo-font/src/Font.ts index c9f1c453e2b8e7..b781d8060c6f25 100644 --- a/packages/expo-font/src/Font.ts +++ b/packages/expo-font/src/Font.ts @@ -228,4 +228,5 @@ export { type FontSource, type FontResource, type UnloadFontOptions, + type ServerFontResourceDescriptor, } from './Font.types'; diff --git a/packages/expo-font/src/Font.types.ts b/packages/expo-font/src/Font.types.ts index 2c8d6726531a9c..59080fc1a6f3b0 100644 --- a/packages/expo-font/src/Font.types.ts +++ b/packages/expo-font/src/Font.types.ts @@ -78,3 +78,17 @@ export enum FontDisplay { export type UnloadFontOptions = Pick; export type UseFontHook = (map: string | Record) => [boolean, Error | null]; + +export type ServerFontResourceDescriptor = + | { + type: 'style'; + css: string; + id: string; + } + | { + type: 'link'; + as: 'font'; + crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined; + href: string; + rel: 'preload'; + }; diff --git a/packages/expo-font/src/server.ts b/packages/expo-font/src/server.ts index d616a6f5fb3463..1afc3d2ee87980 100644 --- a/packages/expo-font/src/server.ts +++ b/packages/expo-font/src/server.ts @@ -1,7 +1,7 @@ import { CodedError, UnavailabilityError } from 'expo-modules-core'; -import ExpoFontLoader, { type ServerFontResourceDescriptor } from './ExpoFontLoader'; -import type { FontSource } from './Font.types'; +import ExpoFontLoader from './ExpoFontLoader'; +import type { FontSource, ServerFontResourceDescriptor } from './Font.types'; import { getAssetForSource, loadSingleFontAsync } from './FontLoader'; /** diff --git a/packages/expo-modules-core/CHANGELOG.md b/packages/expo-modules-core/CHANGELOG.md index 0c2c2705d4aedb..e77ea49a98955d 100644 --- a/packages/expo-modules-core/CHANGELOG.md +++ b/packages/expo-modules-core/CHANGELOG.md @@ -42,6 +42,7 @@ ### 💡 Others +- [iOS] Reduced per-call overhead of invoking native functions from JavaScript. ([#45093](https://github.com/expo/expo/pull/45093) by [@tsapeta](https://github.com/tsapeta)) - [iOS] Fixed precompile build failing on `SwiftUIVirtualViewSharedImpl+Private.h` leaking into the public module umbrella. ([#44993](https://github.com/expo/expo/pull/44993) by [@chrfalch](https://github.com/chrfalch)) - [iOS] Added explicit c++ linkage specifier to `ExpoModulesCore.podspec` to propagate to swift-only targets like Expo Widgets ([#44984](https://github.com/expo/expo/pull/44984) by [@chrfalch](https://github.com/chrfalch)) - [iOS] Rewrote the core type system, definitions, `AppContext`, and worklets integration on top of the new `ExpoModulesJSI` package. ([#44337](https://github.com/expo/expo/pull/44337) by [@tsapeta](https://github.com/tsapeta)) diff --git a/packages/expo-modules-core/ios/Core/AppContext.swift b/packages/expo-modules-core/ios/Core/AppContext.swift index 5daa542a1de1e8..b2f630d00c64ea 100644 --- a/packages/expo-modules-core/ios/Core/AppContext.swift +++ b/packages/expo-modules-core/ios/Core/AppContext.swift @@ -148,7 +148,12 @@ public final class AppContext: NSObject, EXAppContextProtocol, @unchecked Sendab */ internal private(set) lazy var coreModuleHolder = ModuleHolder(appContext: self, module: coreModule, name: nil) - internal private(set) lazy var converter = MainValueConverter(appContext: self) + internal var converter: MainValueConverter { + // MainValueConverter is ~Copyable so it can't be stored in a lazy var (which uses Optional internally). + // This is zero-cost at runtime — the struct contains only an unowned pointer to self, so constructing + // it is a single pointer store with no allocations, reference counting, or weak-ref side table work. + return MainValueConverter(appContext: self) + } /** Designated initializer without modules provider. diff --git a/packages/expo-modules-core/ios/Core/Conversions.swift b/packages/expo-modules-core/ios/Core/Conversions.swift index 2149ac51f779a6..86b5b28f51ab9d 100644 --- a/packages/expo-modules-core/ios/Core/Conversions.swift +++ b/packages/expo-modules-core/ios/Core/Conversions.swift @@ -4,30 +4,31 @@ public struct Conversions { /** Converts an array to tuple. Because of tuples nature, it's not possible to convert an array of any size, so we can support only up to some fixed size. */ - static func toTuple(_ array: [Any?]) throws -> Any? { + @_transparent + static func toTuple(_ array: [Any]) throws -> Args? { switch array.count { case 0: - return () + return () as? Args case 1: - return (array[0]) + return (array[0]) as? Args case 2: - return (array[0], array[1]) + return (array[0], array[1]) as? Args case 3: - return (array[0], array[1], array[2]) + return (array[0], array[1], array[2]) as? Args case 4: - return (array[0], array[1], array[2], array[3]) + return (array[0], array[1], array[2], array[3]) as? Args case 5: - return (array[0], array[1], array[2], array[3], array[4]) + return (array[0], array[1], array[2], array[3], array[4]) as? Args case 6: - return (array[0], array[1], array[2], array[3], array[4], array[5]) + return (array[0], array[1], array[2], array[3], array[4], array[5]) as? Args case 7: - return (array[0], array[1], array[2], array[3], array[4], array[5], array[6]) + return (array[0], array[1], array[2], array[3], array[4], array[5], array[6]) as? Args case 8: - return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7]) + return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7]) as? Args case 9: - return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8]) + return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8]) as? Args case 10: - return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8], array[9]) + return (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8], array[9]) as? Args default: throw TooManyArgumentsException((count: array.count, limit: 10)) } diff --git a/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicDictionaryType.swift b/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicDictionaryType.swift index bafda3d9a92c68..a52a21b4da16a7 100644 --- a/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicDictionaryType.swift +++ b/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicDictionaryType.swift @@ -24,12 +24,10 @@ internal struct DynamicDictionaryType: AnyDynamicType { } func cast(jsValue: JavaScriptValue, appContext: AppContext) throws -> Any { - let converter = appContext.converter - if let jsObject = try? jsValue.asObject() { var result: [AnyHashable: Any] = [:] for key in jsObject.getPropertyNames() { - result[key] = try converter.toNative(jsObject.getProperty(key), valueType) + result[key] = try appContext.converter.toNative(jsObject.getProperty(key), valueType) } return result } diff --git a/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicNumberType.swift b/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicNumberType.swift index d95255fd9ed3da..e823bad6663367 100644 --- a/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicNumberType.swift +++ b/packages/expo-modules-core/ios/Core/DynamicTypes/DynamicNumberType.swift @@ -15,13 +15,22 @@ internal struct DynamicNumberType: AnyDynamicType { func cast(jsValue: JavaScriptValue, appContext: AppContext) throws -> Any { if jsValue.isNumber() { + // Fast paths for common types avoid expensive `as? any BinaryFloatingPoint` conformance lookups. + let double = jsValue.getDouble() + if NumberType.self == Double.self { + return double + } + if NumberType.self == Int.self { + return Int(double.rounded()) + } + if NumberType.self == Float.self { + return Float(double) + } if let FloatingPointType = NumberType.self as? any BinaryFloatingPoint.Type { - return FloatingPointType.init(jsValue.getDouble()) + return FloatingPointType.init(double) } if let IntegerType = NumberType.self as? any BinaryInteger.Type { - // JS stores all numbers as doubles, so using `getDouble` makes more sense - // than `getInt` and lets us do schoolbook rounding instead of floor. - return IntegerType.init(jsValue.getDouble().rounded()) + return IntegerType.init(double.rounded()) } } throw Conversions.ConversionToNativeFailedException((kind: jsValue.kind, nativeType: numberType)) @@ -59,6 +68,16 @@ internal struct DynamicNumberType: AnyDynamicType { } func castToJS(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue { + // Fast paths for common types avoid expensive `as? any BinaryFloatingPoint` conformance lookups. + if let value = value as? Double { + return .number(value) + } + if let value = value as? Int { + return .number(Double(value)) + } + if let value = value as? Float { + return .number(Double(value)) + } if let value = value as? any BinaryFloatingPoint { return .number(Double(value)) } diff --git a/packages/expo-modules-core/ios/Core/Functions/AnyFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/AnyFunctionDefinition.swift index 72fbd5ce94f7fb..a9c8f760cb2763 100644 --- a/packages/expo-modules-core/ios/Core/Functions/AnyFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/AnyFunctionDefinition.swift @@ -36,25 +36,6 @@ internal protocol AnyFunctionDefinition: AnyDefinition, JavaScriptObjectBuilder, var takesOwner: Bool { get set } } -extension AnyFunctionDefinition { - var requiredArgumentsCount: Int { - var trailingOptionalArgumentsCount: Int = 0 - - for dynamicArgumentType in dynamicArgumentTypes.reversed() { - if dynamicArgumentType is DynamicOptionalType { - trailingOptionalArgumentsCount += 1 - } else { - break - } - } - return argumentsCount - trailingOptionalArgumentsCount - } - - var argumentsCount: Int { - return dynamicArgumentTypes.count - } -} - internal final class FunctionCallException: GenericException, @unchecked Sendable { override var reason: String { "Calling the '\(param)' function has failed" diff --git a/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift index a767508c52b4d1..6977fb25c093c1 100644 --- a/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift @@ -59,6 +59,12 @@ public class AsyncFunctionDefinition: AnyAsyncFu self.dynamicArgumentTypes = dynamicArgumentTypes self.returnType = returnType self.body = body + + self.trailingOptionalArgumentsCount = dynamicArgumentTypes + .dropLast(takesPromise ? 1 : 0) + .reversed() + .prefix(while: { $0 is DynamicOptionalType }) + .count } // MARK: - AnyFunction @@ -67,10 +73,16 @@ public class AsyncFunctionDefinition: AnyAsyncFu let dynamicArgumentTypes: [AnyDynamicType] + private let trailingOptionalArgumentsCount: Int + var argumentsCount: Int { return dynamicArgumentTypes.count - (takesOwner ? 1 : 0) - (takesPromise ? 1 : 0) } + var requiredArgumentsCount: Int { + return argumentsCount - trailingOptionalArgumentsCount + } + var takesOwner: Bool = false @JavaScriptActor @@ -103,7 +115,7 @@ public class AsyncFunctionDefinition: AnyAsyncFu let returnedValue: ReturnType do { - guard let argumentsTuple = try Conversions.toTuple(nativeArguments) as? Args else { + guard let argumentsTuple: Args = try Conversions.toTuple(nativeArguments) else { throw ArgumentConversionException() } returnedValue = try body(argumentsTuple) @@ -198,24 +210,4 @@ public class AsyncFunctionDefinition: AnyAsyncFu } } -extension AsyncFunctionDefinition { - var requiredArgumentsCount: Int { - var trailingOptionalArgumentsCount: Int = 0 - - let reversedArgumentTypes = dynamicArgumentTypes.reversed() - let reversedArgumentsToIterate: any Sequence = takesPromise - ? reversedArgumentTypes.dropFirst() - : reversedArgumentTypes - - for dynamicArgumentType in reversedArgumentsToIterate { - if dynamicArgumentType is DynamicOptionalType { - trailingOptionalArgumentsCount += 1 - } else { - break - } - } - - return argumentsCount - trailingOptionalArgumentsCount - } -} diff --git a/packages/expo-modules-core/ios/Core/Functions/ConcurrentFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/ConcurrentFunctionDefinition.swift index 6cc332da95d911..6530ce7036a5a0 100644 --- a/packages/expo-modules-core/ios/Core/Functions/ConcurrentFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/ConcurrentFunctionDefinition.swift @@ -30,6 +30,10 @@ public class ConcurrentFunctionDefinition: AnyCo self.name = name self.body = body self.dynamicArgumentTypes = dynamicArgumentTypes + self.trailingOptionalArgumentsCount = dynamicArgumentTypes + .reversed() + .prefix(while: { $0 is DynamicOptionalType }) + .count } // MARK: - AnyFunction @@ -38,30 +42,38 @@ public class ConcurrentFunctionDefinition: AnyCo let dynamicArgumentTypes: [AnyDynamicType] + private let trailingOptionalArgumentsCount: Int + var argumentsCount: Int { return dynamicArgumentTypes.count - (takesOwner ? 1 : 0) } + var requiredArgumentsCount: Int { + return argumentsCount - trailingOptionalArgumentsCount + } + var takesOwner: Bool = false var requiresMainActor: Bool = false @JavaScriptActor func call(_ appContext: AppContext, this: JavaScriptValue, arguments: consuming JavaScriptValuesBuffer) async throws -> JavaScriptValue { - let nativeArguments = NonisolatedUnsafeVar<[Any]>([]) - do { try validateArgumentsNumber(function: self, received: arguments.count) // Arguments must be converted on the JS thread, before we jump to another thread. - nativeArguments.value = try toNativeClosureArguments(converter: appContext.converter, fn: self, this: this, arguments: arguments) + let nativeArguments = try toNativeClosureArguments(converter: appContext.converter, fn: self, this: this, arguments: arguments) - guard let argumentsTuple = try Conversions.toTuple(nativeArguments.value) as? Args else { + guard let argumentsTuple: Args = try Conversions.toTuple(nativeArguments) else { throw ArgumentConversionException() } - let returnValue = if requiresMainActor { - try await callBodyOnMainActor(consume argumentsTuple) + // Safe to mark as nonisolated(unsafe) — the tuple contains fully converted native values + // with no references back to JS objects, so it can safely cross the actor boundary. + nonisolated(unsafe) let nonisolatedArgumentsTuple = argumentsTuple + + let returnValue: ReturnType = if requiresMainActor { + try await callBodyOnMainActor(nonisolatedArgumentsTuple) } else { - try await body(consume argumentsTuple) + try await body(nonisolatedArgumentsTuple) } return try await appContext.runtime.execute { diff --git a/packages/expo-modules-core/ios/Core/Functions/OptimizedAsyncFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/OptimizedAsyncFunctionDefinition.swift index 09740e77506307..6c97edc2e2655b 100644 --- a/packages/expo-modules-core/ios/Core/Functions/OptimizedAsyncFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/OptimizedAsyncFunctionDefinition.swift @@ -35,6 +35,11 @@ public struct OptimizedAsyncFunctionDefinition: AnyAsyncFunctionDefinition, @unc return argsCount } + public var requiredArgumentsCount: Int { + // Optimized functions don't support optional arguments, so all args are required. + return argsCount + } + public var takesOwner: Bool = false public func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) { diff --git a/packages/expo-modules-core/ios/Core/Functions/OptimizedSyncFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/OptimizedSyncFunctionDefinition.swift index a8b8873bab18d7..b4d6d004e66ba3 100644 --- a/packages/expo-modules-core/ios/Core/Functions/OptimizedSyncFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/OptimizedSyncFunctionDefinition.swift @@ -33,6 +33,11 @@ public struct OptimizedSyncFunctionDefinition: AnySyncFunctionDefinition, @unche return argsCount } + public var requiredArgumentsCount: Int { + // Optimized functions don't support optional arguments, so all args are required. + return argsCount + } + public var takesOwner: Bool = false public func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) { diff --git a/packages/expo-modules-core/ios/Core/Functions/SyncFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/SyncFunctionDefinition.swift index 20834c2d2f4e47..81cbfd1383b498 100644 --- a/packages/expo-modules-core/ios/Core/Functions/SyncFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/SyncFunctionDefinition.swift @@ -52,6 +52,10 @@ public class SyncFunctionDefinition: AnySyncFunc self.dynamicArgumentTypes = dynamicArgumentTypes self.returnType = returnType self.body = body + self.trailingOptionalArgumentsCount = dynamicArgumentTypes + .reversed() + .prefix(while: { $0 is DynamicOptionalType }) + .count } // MARK: - AnyFunction @@ -62,21 +66,27 @@ public class SyncFunctionDefinition: AnySyncFunc let returnType: AnyDynamicType + private let trailingOptionalArgumentsCount: Int + var argumentsCount: Int { return dynamicArgumentTypes.count - (takesOwner ? 1 : 0) } + var requiredArgumentsCount: Int { + return argumentsCount - trailingOptionalArgumentsCount + } + var takesOwner: Bool = false // MARK: - AnySyncFunctionDefinition @JavaScriptActor + @discardableResult func runBody(_ appContext: AppContext, in runtime: JavaScriptRuntime, this: JavaScriptValue, arguments: consuming JavaScriptValuesBuffer) throws(Exception) -> Any { do { try validateArgumentsNumber(function: self, received: arguments.count) let nativeArguments = try toNativeClosureArguments(converter: appContext.converter, fn: self, this: this, arguments: arguments) - - guard let argumentsTuple = try Conversions.toTuple(nativeArguments) as? Args else { + guard let argumentsTuple: Args = try Conversions.toTuple(nativeArguments) else { throw ArgumentConversionException() } return try body(argumentsTuple) as Any @@ -125,16 +135,20 @@ public class SyncFunctionDefinition: AnySyncFunc } } +@_transparent @JavaScriptActor -internal func toNativeClosureArguments( - converter: MainValueConverter, - fn: AnyFunctionDefinition, +internal func toNativeClosureArguments( + converter: borrowing MainValueConverter, + fn: borrowing F, this: JavaScriptValue, arguments: borrowing JavaScriptValuesBuffer ) throws -> [Any] { - // This array will include the owner (if needed) and function arguments. + if !fn.takesOwner, fn.argumentsCount == 0 { + return [] + } + var nativeArguments: [Any] = [] - let receivedArgumentsCount = arguments.count + nativeArguments.reserveCapacity(fn.dynamicArgumentTypes.count) // If the function takes the owner, convert it and add to the final arguments. if fn.takesOwner, !this.isUndefined(), let ownerType = fn.dynamicArgumentTypes.first { @@ -142,16 +156,20 @@ internal func toNativeClosureArguments( nativeArguments.append(nativeOwner) } - // Convert JS values to non-JS native types desired by the function. - let dynamicTypesWithoutOwner = Array(fn.dynamicArgumentTypes.dropFirst(nativeArguments.count)) - nativeArguments.append( - contentsOf: try converter.toNative(arguments, dynamicTypesWithoutOwner) - ) + // Convert JS values to native types desired by the function. + let typeOffset = nativeArguments.count + for i in 0..(function: borrowing F, received: Int) throws { let argumentsCount = function.argumentsCount let requiredArgumentsCount = function.requiredArgumentsCount diff --git a/packages/expo-modules-core/ios/Core/MainValueConverter.swift b/packages/expo-modules-core/ios/Core/MainValueConverter.swift index 2c502ca2f9f344..3826037f136518 100644 --- a/packages/expo-modules-core/ios/Core/MainValueConverter.swift +++ b/packages/expo-modules-core/ios/Core/MainValueConverter.swift @@ -5,8 +5,9 @@ import ExpoModulesJSI /** A converter associated with the specific app context that delegates value conversions to the dynamic type converters. */ -public struct MainValueConverter { - private(set) weak var appContext: AppContext? +public struct MainValueConverter: ~Copyable { + // Safe to use unowned — the converter is a lazy property of AppContext, so AppContext always outlives it. + unowned let appContext: AppContext /** Casts the given JavaScriptValue to a non-JS value. @@ -14,9 +15,6 @@ public struct MainValueConverter { */ @JavaScriptActor public func toNative(_ value: JavaScriptValue, _ type: AnyDynamicType) throws -> Any { - guard let appContext else { - throw Exceptions.AppContextLost() - } let rawValue = try type.cast(jsValue: value, appContext: appContext) return try type.cast(rawValue, appContext: appContext) } @@ -41,10 +39,8 @@ public struct MainValueConverter { /** Converts the given value to the type compatible with JavaScript. */ + @JavaScriptActor public func toJS(_ value: Any, _ type: AnyDynamicType) throws -> JavaScriptValue { - guard let appContext else { - throw Exceptions.AppContextLost() - } let result = Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: type) return try type.castToJS(result, appContext: appContext) } diff --git a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift index 6b873610f03b98..7ff83af73bd445 100644 --- a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +++ b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift @@ -286,16 +286,14 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable { return createFunction(name) { this, arguments in let promise = JavaScriptPromise(self) - // Need to switch to reference semantics as Task escapes the closure (consumes on capture). // Arguments buffer needs to be copied to ensure safe async access. - let thisRef = this.ref() let argumentsRef = arguments.copy().ref() // Switch to asynchronous context. self.schedule(taskName: "[JS] Async function \(name)") { // Invoke the asynchronous function and resolve/reject the promise. do { - let result = try await function(thisRef.take(), argumentsRef.take()) + let result = try await function(this, argumentsRef.take()) promise.resolve(result) } catch { promise.reject(error) diff --git a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptValuesBuffer.swift b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptValuesBuffer.swift index 1b31dbfee9914e..d6c9b0bbcff955 100644 --- a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptValuesBuffer.swift +++ b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptValuesBuffer.swift @@ -6,7 +6,10 @@ internal import jsi Used mainly to pass function arguments from C++ to Swift. */ public struct JavaScriptValuesBuffer: JavaScriptType, ~Copyable { - internal weak let runtime: JavaScriptRuntime? + // Safe to use unowned — the buffer's lifetime is scoped to a host function call, + // so the runtime is always alive while the buffer exists. + internal unowned let runtime: JavaScriptRuntime + internal nonisolated(unsafe) let bufferPointer: UnsafeMutableBufferPointer private let ownsMemory: Bool @@ -46,21 +49,15 @@ public struct JavaScriptValuesBuffer: JavaScriptType, ~Copyable { } public subscript(index: Int) -> JavaScriptValue { - guard let runtime else { - FatalError.runtimeLost() - } return JavaScriptValue(runtime, bufferPointer[index]) } @discardableResult internal consuming func set(value: borrowing T, atIndex index: Int) -> JavaScriptValuesBuffer where T: ~Copyable { - guard let jsiRuntime = runtime?.pointee else { - FatalError.runtimeLost() - } guard (0.. JavaScriptValuesBuffer { - guard let runtime else { - FatalError.runtimeLost() - } let bufferCopy = JavaScriptValuesBuffer.copying(in: runtime, buffer: bufferPointer) return JavaScriptValuesBuffer(runtime, buffer: bufferCopy, ownsMemory: true) } diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/ExpoManifest.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/ExpoManifest.kt index ed619109ffbe48..8a08d7428e1421 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/ExpoManifest.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/ExpoManifest.kt @@ -7,7 +7,6 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @JvmInline @@ -44,12 +43,6 @@ internal value class ExpoManifest( */ val useOpenTelemetry: Boolean get() = getProperty("extra.eas.observe.useOpenTelemetry")?.jsonPrimitive?.booleanOrNull ?: true - - /** - * Gets the extra.eas.observe.enableInDebug from the manifest. Defaults to false. - */ - val enableInDebug: Boolean - get() = getProperty("extra.eas.observe.enableInDebug")?.jsonPrimitive?.booleanOrNull ?: false } internal fun getManifest(constants: ConstantsInterface?): ExpoManifest? { diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt index 09d192e3c3e91c..1d5c773927646c 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityBackgroundWorker.kt @@ -32,7 +32,6 @@ class ObservabilityBackgroundWorker( private val observabilityManager: BaseObservabilityManager? = run { val projectId = inputData.getString("projectId") val baseUrl = inputData.getString("baseUrl") - val enableInDebug = inputData.getBoolean("enableInDebug", false) val useOpenTelemetry = inputData.getBoolean("useOpenTelemetry", false) if (projectId == null || baseUrl == null) { @@ -51,7 +50,7 @@ class ObservabilityBackgroundWorker( sessionManager = sessionManager, pendingMetricsManager = pendingMetricsManager, baseUrl = baseUrl, - enableInDebug = enableInDebug, + isDebugBuild = BuildConfig.DEBUG, useOpenTelemetry = useOpenTelemetry ) } @@ -98,7 +97,6 @@ class ObservabilityBackgroundWorker( context: Context, projectId: String, baseUrl: String, - enableInDebug: Boolean = false, useOpenTelemetry: Boolean = false ) { val constraints = Constraints @@ -109,7 +107,6 @@ class ObservabilityBackgroundWorker( val data = workDataOf( Pair("projectId", projectId), Pair("baseUrl", baseUrl), - Pair("enableInDebug", enableInDebug), Pair("useOpenTelemetry", useOpenTelemetry) ) diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityManager.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityManager.kt index e29143f15d5667..2b7370795bf0fb 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityManager.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObservabilityManager.kt @@ -12,7 +12,6 @@ class ObservabilityManager( val sessionManager: SessionManager ) { private val baseManager: BaseObservabilityManager - private val enableInDebug: Boolean private val useOpenTelemetry: Boolean // TODO: Can this information change during expo module lifecycle? @@ -27,7 +26,6 @@ class ObservabilityManager( "Project ID is required to send observability metrics. Make sure you have configured it correctly in app.json." } val baseUrl = manifest.baseUrl ?: OBSERVE_DEFAULT_BASE_URL - enableInDebug = manifest.enableInDebug useOpenTelemetry = manifest.useOpenTelemetry val pendingMetricsManager = PendingMetricsManager(context) @@ -38,7 +36,7 @@ class ObservabilityManager( pendingMetricsManager = pendingMetricsManager, projectId = projectId, baseUrl = baseUrl, - enableInDebug = enableInDebug, + isDebugBuild = BuildConfig.DEBUG, useOpenTelemetry = useOpenTelemetry ) @@ -56,7 +54,6 @@ class ObservabilityManager( context = context, projectId = baseManager.projectId, baseUrl = baseManager.baseUrl, - enableInDebug = enableInDebug, useOpenTelemetry = useOpenTelemetry ) } @@ -68,7 +65,7 @@ class BaseObservabilityManager( private val pendingMetricsManager: PendingMetricsManager, val projectId: String, val baseUrl: String, - private val enableInDebug: Boolean = false, + private val isDebugBuild: Boolean = false, private val useOpenTelemetry: Boolean = false ) { private val eventDispatcher = EventDispatcher( @@ -84,8 +81,9 @@ class BaseObservabilityManager( return } - // When disabled, mark pending metrics as sent without dispatching - val dispatchingEnabled = ObservePreferences.getConfig(context)?.dispatchingEnabled ?: true + // Single dispatch gate. When unset, defaults to off in debug builds and on otherwise so + // dev metrics aren't shipped without explicit opt-in. + val dispatchingEnabled = ObservePreferences.getConfig(context)?.dispatchingEnabled ?: !isDebugBuild if (!dispatchingEnabled) { pendingMetricsManager.removePendingMetrics(pendingIds) return @@ -104,22 +102,7 @@ class BaseObservabilityManager( return } - val (toDispatch, toSkip) = if (enableInDebug) { - Pair(sessionsWithPendingMetrics, emptyList()) - } else { - sessionsWithPendingMetrics.partition { it.session.environment != "development" } - } - - // Remove skipped (dev) metrics from pending table without dispatching - toSkip - .flatMap { it.metrics } - .map { it.metricId } - .takeIf { it.isNotEmpty() } - ?.let { pendingMetricsManager.removePendingMetrics(it) } - - if (toDispatch.isEmpty()) return - - val events = toDispatch.map { sessionWithMetrics -> + val events = sessionsWithPendingMetrics.map { sessionWithMetrics -> Event( metadata = Metadata.fromSessionMetadata(sessionWithMetrics.session), metrics = sessionWithMetrics.metrics.map { EASMetric.fromMetric(it) } @@ -127,7 +110,7 @@ class BaseObservabilityManager( } if (eventDispatcher.dispatch(events)) { - val dispatchedMetricIds = toDispatch.flatMap { it.metrics }.map { it.metricId } + val dispatchedMetricIds = sessionsWithPendingMetrics.flatMap { it.metrics }.map { it.metricId } pendingMetricsManager.removePendingMetrics(dispatchedMetricIds) } } diff --git a/packages/expo-observe/android/src/test/java/expo/modules/observe/BaseObservabilityManagerTest.kt b/packages/expo-observe/android/src/test/java/expo/modules/observe/BaseObservabilityManagerTest.kt index 760a22c12a58bb..0bc25b4cf4cffa 100644 --- a/packages/expo-observe/android/src/test/java/expo/modules/observe/BaseObservabilityManagerTest.kt +++ b/packages/expo-observe/android/src/test/java/expo/modules/observe/BaseObservabilityManagerTest.kt @@ -46,152 +46,75 @@ class BaseObservabilityManagerTest { unmockkAll() } - // region enableInDebug tests + // region dispatchingEnabled tests @Test - fun `when enableInDebug is false, development sessions are skipped`() = + fun `when dispatchingEnabled is false, pending metrics are removed without dispatching`() = runTest { // Arrange - val devMetric = createMetric("metric1", metricId = "dev-metric-id") - val prodMetric = createMetric("metric2", metricId = "prod-metric-id") - val devSession = createSessionWithMetrics( - sessionId = "dev-session", - environment = "development", - metrics = listOf(devMetric) - ) - val prodSession = createSessionWithMetrics( - sessionId = "prod-session", - environment = "production", - metrics = listOf(prodMetric) - ) - - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("dev-metric-id", "prod-metric-id") - coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(devSession, prodSession) - coEvery { mockEventDispatcher.dispatch(any()) } returns true + every { ObservePreferences.getConfig(any()) } returns PersistedConfig(dispatchingEnabled = false) + coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("id1", "id2") val removedIds = mutableListOf() coEvery { mockPendingMetricsManager.removePendingMetrics(any()) } answers { removedIds.addAll(firstArg>()) } - val manager = createManager(enableInDebug = false) + val manager = createManager() // Act manager.dispatchUnsentMetrics() - // Assert - only production session dispatched - coVerify { - mockEventDispatcher.dispatch( - match { events -> - events.size == 1 && events[0].metadata.environment == "production" - } - ) - } - - // Assert - ALL metric IDs are removed from pending (both dev and prod) - assertEquals(2, removedIds.size) - assertTrue("Dev metric should be removed from pending", removedIds.contains("dev-metric-id")) - assertTrue("Prod metric should be removed from pending", removedIds.contains("prod-metric-id")) - } - - @Test - fun `when enableInDebug is true, development sessions are dispatched`() = - runTest { - // Arrange - val devMetric = createMetric("metric1", metricId = "dev-metric-id") - val prodMetric = createMetric("metric2", metricId = "prod-metric-id") - val devSession = createSessionWithMetrics( - sessionId = "dev-session", - environment = "development", - metrics = listOf(devMetric) - ) - val prodSession = createSessionWithMetrics( - sessionId = "prod-session", - environment = "production", - metrics = listOf(prodMetric) - ) - - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("dev-metric-id", "prod-metric-id") - coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(devSession, prodSession) - coEvery { mockEventDispatcher.dispatch(any()) } returns true - - val removedIds = mutableListOf() - coEvery { mockPendingMetricsManager.removePendingMetrics(any()) } answers { - removedIds.addAll(firstArg>()) - } - - val manager = createManager(enableInDebug = true) - - // Act - manager.dispatchUnsentMetrics() + // Assert - dispatch is never called + coVerify(exactly = 0) { mockEventDispatcher.dispatch(any()) } - // Assert - both sessions dispatched - coVerify { - mockEventDispatcher.dispatch( - match { events -> - events.size == 2 - } - ) - } + // Assert - sessions are never fetched + coVerify(exactly = 0) { mockSessionManager.getSessionsWithMetrics(any()) } - // Assert - ALL metric IDs are removed from pending + // Assert - all pending metric IDs are removed assertEquals(2, removedIds.size) - assertTrue("Dev metric should be removed from pending", removedIds.contains("dev-metric-id")) - assertTrue("Prod metric should be removed from pending", removedIds.contains("prod-metric-id")) + assertTrue("id1 should be removed", removedIds.contains("id1")) + assertTrue("id2 should be removed", removedIds.contains("id2")) } @Test - fun `when enableInDebug is false and all sessions are development, nothing is dispatched`() = + fun `when dispatchingEnabled is unset and isDebugBuild is true, pending metrics are removed without dispatching`() = runTest { - // Arrange - val devMetric1 = createMetric("metric1", metricId = "dev-metric-1") - val devMetric2 = createMetric("metric2", metricId = "dev-metric-2") - val devSession1 = createSessionWithMetrics( - sessionId = "dev-session-1", - environment = "development", - metrics = listOf(devMetric1) - ) - val devSession2 = createSessionWithMetrics( - sessionId = "dev-session-2", - environment = "development", - metrics = listOf(devMetric2) - ) - - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("dev-metric-1", "dev-metric-2") - coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(devSession1, devSession2) + // Arrange — debug builds default to off so dev metrics don't ship without explicit opt-in. + every { ObservePreferences.getConfig(any()) } returns null + coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("id1", "id2") val removedIds = mutableListOf() coEvery { mockPendingMetricsManager.removePendingMetrics(any()) } answers { removedIds.addAll(firstArg>()) } - val manager = createManager(enableInDebug = false) + val manager = createManager(isDebugBuild = true) // Act manager.dispatchUnsentMetrics() - // Assert - no dispatch call made + // Assert — short-circuit: no session lookup, no dispatch, single removePendingMetrics call. coVerify(exactly = 0) { mockEventDispatcher.dispatch(any()) } - - // Assert - ALL development metric IDs are removed from pending + coVerify(exactly = 0) { mockSessionManager.getSessionsWithMetrics(any()) } + coVerify(exactly = 1) { mockPendingMetricsManager.removePendingMetrics(listOf("id1", "id2")) } assertEquals(2, removedIds.size) - assertTrue("Dev metric 1 should be removed from pending", removedIds.contains("dev-metric-1")) - assertTrue("Dev metric 2 should be removed from pending", removedIds.contains("dev-metric-2")) } @Test - fun `when enableInDebug is false, preview environment sessions are dispatched`() = + fun `when dispatchingEnabled is true and isDebugBuild is true, metrics are dispatched`() = runTest { - // Arrange - val previewMetric = createMetric("metric1", metricId = "preview-metric-id") - val previewSession = createSessionWithMetrics( - sessionId = "preview-session", - environment = "preview", - metrics = listOf(previewMetric) + // Arrange — explicit opt-in lifts the debug default. + every { ObservePreferences.getConfig(any()) } returns PersistedConfig(dispatchingEnabled = true) + val devMetric = createMetric("metric1", metricId = "dev-metric-id") + val devSession = createSessionWithMetrics( + sessionId = "dev-session", + environment = "development", + metrics = listOf(devMetric) ) - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("preview-metric-id") - coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(previewSession) + coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("dev-metric-id") + coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(devSession) coEvery { mockEventDispatcher.dispatch(any()) } returns true val removedIds = mutableListOf() @@ -199,76 +122,52 @@ class BaseObservabilityManagerTest { removedIds.addAll(firstArg>()) } - val manager = createManager(enableInDebug = false) + val manager = createManager(isDebugBuild = true) // Act manager.dispatchUnsentMetrics() - // Assert - preview session dispatched + // Assert coVerify { mockEventDispatcher.dispatch( match { events -> - events.size == 1 && events[0].metadata.environment == "preview" + events.size == 1 && events[0].metadata.environment == "development" } ) } - - // Assert - preview metric ID is removed from pending assertEquals(1, removedIds.size) - assertTrue("Preview metric should be removed from pending", removedIds.contains("preview-metric-id")) + assertTrue(removedIds.contains("dev-metric-id")) } - // endregion - - // region enabled tests - @Test - fun `when enabled is false, pending metrics are removed without dispatching`() = + fun `when dispatchingEnabled is unset and isDebugBuild is false, metrics are dispatched`() = runTest { - // Arrange - every { ObservePreferences.getConfig(any()) } returns PersistedConfig(dispatchingEnabled = false) - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("id1", "id2") - - val removedIds = mutableListOf() - coEvery { mockPendingMetricsManager.removePendingMetrics(any()) } answers { - removedIds.addAll(firstArg>()) - } - - val manager = createManager() - - // Act - manager.dispatchUnsentMetrics() - - // Assert - dispatch is never called - coVerify(exactly = 0) { mockEventDispatcher.dispatch(any()) } - - // Assert - sessions are never fetched - coVerify(exactly = 0) { mockSessionManager.getSessionsWithMetrics(any()) } - - // Assert - all pending metric IDs are removed - assertEquals(2, removedIds.size) - assertTrue("id1 should be removed", removedIds.contains("id1")) - assertTrue("id2 should be removed", removedIds.contains("id2")) - } - - @Test - fun `when enabled is false, enableInDebug has no effect`() = - runTest { - // Arrange — enabled=false takes precedence over enableInDebug=true - every { ObservePreferences.getConfig(any()) } returns PersistedConfig(dispatchingEnabled = false) - coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("id1") + // Arrange — release builds default to on. + every { ObservePreferences.getConfig(any()) } returns null + val prodMetric = createMetric("metric1", metricId = "prod-metric-id") + val prodSession = createSessionWithMetrics( + sessionId = "prod-session", + environment = "production", + metrics = listOf(prodMetric) + ) - coEvery { mockPendingMetricsManager.removePendingMetrics(any()) } just runs + coEvery { mockPendingMetricsManager.getAllPendingMetricIds() } returns listOf("prod-metric-id") + coEvery { mockSessionManager.getSessionsWithMetrics(any()) } returns listOf(prodSession) + coEvery { mockEventDispatcher.dispatch(any()) } returns true - val manager = createManager(enableInDebug = true) + val manager = createManager(isDebugBuild = false) // Act manager.dispatchUnsentMetrics() - // Assert — still short-circuits, no dispatch - coVerify(exactly = 0) { mockEventDispatcher.dispatch(any()) } - coVerify(exactly = 0) { mockSessionManager.getSessionsWithMetrics(any()) } - coVerify(exactly = 1) { mockPendingMetricsManager.removePendingMetrics(listOf("id1")) } + // Assert + coVerify { + mockEventDispatcher.dispatch( + match { events -> + events.size == 1 && events[0].metadata.environment == "production" + } + ) + } } // endregion @@ -682,14 +581,14 @@ class BaseObservabilityManagerTest { // region Helper methods - private fun createManager(enableInDebug: Boolean = false): BaseObservabilityManager { + private fun createManager(isDebugBuild: Boolean = false): BaseObservabilityManager { val manager = BaseObservabilityManager( context = mockContext, sessionManager = mockSessionManager, pendingMetricsManager = mockPendingMetricsManager, projectId = testProjectId, baseUrl = testBaseUrl, - enableInDebug = enableInDebug + isDebugBuild = isDebugBuild ) // Replace the internal EventDispatcher with our mock val field = BaseObservabilityManager::class.java.getDeclaredField("eventDispatcher") diff --git a/packages/expo-observe/build/types.d.ts b/packages/expo-observe/build/types.d.ts index 8cf76cdf00a415..5d1443ec71a94e 100644 --- a/packages/expo-observe/build/types.d.ts +++ b/packages/expo-observe/build/types.d.ts @@ -8,7 +8,11 @@ export type Config = { /** * Whether to enable dispatching events to the server * - * @default true for production, false for development + * When `false`, any pending metrics are marked as sent without being dispatched + * and no further metrics are dispatched until this is set back to `true`. + * + * When unset, defaults to `false` for debug builds and `true` for release builds + * so dev metrics aren't shipped without explicit opt-in. */ dispatchingEnabled?: boolean; }; diff --git a/packages/expo-observe/build/types.d.ts.map b/packages/expo-observe/build/types.d.ts.map index 4775c9918bea56..dc5a27fd682f19 100644 --- a/packages/expo-observe/build/types.d.ts.map +++ b/packages/expo-observe/build/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC"} \ No newline at end of file diff --git a/packages/expo-observe/build/types.js.map b/packages/expo-observe/build/types.js.map index a65a00e309fc9e..db619396f751fc 100644 --- a/packages/expo-observe/build/types.js.map +++ b/packages/expo-observe/build/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Config = {\n /**\n * The environment for observability events\n *\n * @default process.env.NODE_ENV\n */\n environment?: string;\n /**\n * Whether to enable dispatching events to the server\n *\n * @default true for production, false for development\n */\n dispatchingEnabled?: boolean;\n};\n\nexport interface ExpoObserveModuleType {\n dispatchEvents(): Promise;\n /**\n * Configures observability settings.\n */\n configure(config: Config): void;\n}\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Config = {\n /**\n * The environment for observability events\n *\n * @default process.env.NODE_ENV\n */\n environment?: string;\n /**\n * Whether to enable dispatching events to the server\n *\n * When `false`, any pending metrics are marked as sent without being dispatched\n * and no further metrics are dispatched until this is set back to `true`.\n *\n * When unset, defaults to `false` for debug builds and `true` for release builds\n * so dev metrics aren't shipped without explicit opt-in.\n */\n dispatchingEnabled?: boolean;\n};\n\nexport interface ExpoObserveModuleType {\n dispatchEvents(): Promise;\n /**\n * Configures observability settings.\n */\n configure(config: Config): void;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-observe/ios/Observability.swift b/packages/expo-observe/ios/Observability.swift index e003de42cde975..122697350fc58a 100644 --- a/packages/expo-observe/ios/Observability.swift +++ b/packages/expo-observe/ios/Observability.swift @@ -5,10 +5,16 @@ import ExpoAppMetrics internal struct ObservabilityManager { private static let easClientId = EASClientID.uuid().uuidString private static var endpointUrl: URL? = nil - private static var enableInDebug: Bool = false private static var projectId: String? = nil private static var useOpenTelemetry = false + // Determined at compile time of the host app's binary. + #if DEBUG + private static let isDebugBuild: Bool = true + #else + private static let isDebugBuild: Bool = false + #endif + /** Returns entries from AppMetrics storage that have not been dispatched yet. */ @@ -38,21 +44,22 @@ internal struct ObservabilityManager { return } do { - // Filter entries based on environment if enableInDebug is false - let entriesToDispatch = - enableInDebug - ? entries - : entries.filter { $0.environment != "development" } + // Single dispatch gate. When unset, defaults to off in debug builds and on otherwise so + // dev metrics aren't shipped without explicit opt-in. + let dispatchingEnabled = ObserveUserDefaults.config?.dispatchingEnabled ?? !isDebugBuild + if !dispatchingEnabled { + // Mark all pending entries as dispatched without sending them + ObserveUserDefaults.lastDispatchedEntryId = entries.first?.id ?? -1 + return + } - let events = entriesToDispatch.map { entry in + let events = entries.map { entry in return Event.create( app: entry.app, device: entry.device, sessions: entry.sessions, environment: entry.environment) } - let dispatchingEnabled = ObserveUserDefaults.config?.dispatchingEnabled ?? true - if events.isEmpty || !dispatchingEnabled { - // All entries were filtered out or dispatching is disabled — mark as dispatched + if events.isEmpty { ObserveUserDefaults.lastDispatchedEntryId = entries.first?.id ?? -1 return } @@ -103,13 +110,6 @@ internal struct ObservabilityManager { } } - internal nonisolated static func setEnableInDebug(_ enabled: Bool?) { - let enabled = enabled ?? false - AppMetricsActor.isolated { - self.enableInDebug = enabled - } - } - internal nonisolated static func setUseOpenTelemetry(_ enabled: Bool?) { let enabled = enabled ?? true AppMetricsActor.isolated { diff --git a/packages/expo-observe/ios/ObserveModule.swift b/packages/expo-observe/ios/ObserveModule.swift index a97a0258292ea8..1ae8a914a4a9c2 100644 --- a/packages/expo-observe/ios/ObserveModule.swift +++ b/packages/expo-observe/ios/ObserveModule.swift @@ -19,9 +19,7 @@ public final class ObserveModule: Module { // which is not great as it requires the app context. Ideally if we move EAS-specific config to `expo-eas-client` at some point. if let manifest = getManifest(appContext), let projectId = getProjectId(manifest: manifest) { let baseUrl = getBaseUrl(manifest) - let enableInDebug = getEnableInDebug(manifest) let useOpenTelemetry = getUseOpenTelemetry(manifest) - ObservabilityManager.setEnableInDebug(enableInDebug) ObservabilityManager.setUseOpenTelemetry(useOpenTelemetry) // Set the endpoint URL after enabling Open Telemetry ObservabilityManager.setEndpointUrl(baseUrl, projectId: projectId) @@ -65,10 +63,6 @@ private func getBaseUrl(_ manifest: [String: Any]) -> String? { return getManifestProperty("extra.eas.observe.endpointUrl", manifest) as? String } -private func getEnableInDebug(_ manifest: [String: Any]) -> Bool? { - return getManifestProperty("extra.eas.observe.enableInDebug", manifest) as? Bool -} - private func getUseOpenTelemetry(_ manifest: [String: Any]) -> Bool? { return getManifestProperty("extra.eas.observe.useOpenTelemetry", manifest) as? Bool } diff --git a/packages/expo-observe/ios/ObserveUserDefaults.swift b/packages/expo-observe/ios/ObserveUserDefaults.swift index a5b950c3e6f994..ea871b3c50f358 100644 --- a/packages/expo-observe/ios/ObserveUserDefaults.swift +++ b/packages/expo-observe/ios/ObserveUserDefaults.swift @@ -3,7 +3,7 @@ import ExpoAppMetrics /** - Snapshot of the last `configure(...)` payload. + Snapshot of the last `configure(...)` payload. */ internal struct PersistedConfig: Codable { var dispatchingEnabled: Bool? diff --git a/packages/expo-observe/src/types.ts b/packages/expo-observe/src/types.ts index dc46078c65efb0..f9434d359701d0 100644 --- a/packages/expo-observe/src/types.ts +++ b/packages/expo-observe/src/types.ts @@ -8,7 +8,11 @@ export type Config = { /** * Whether to enable dispatching events to the server * - * @default true for production, false for development + * When `false`, any pending metrics are marked as sent without being dispatched + * and no further metrics are dispatched until this is set back to `true`. + * + * When unset, defaults to `false` for debug builds and `true` for release builds + * so dev metrics aren't shipped without explicit opt-in. */ dispatchingEnabled?: boolean; }; diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index 4c8d9ec3fe35b4..0497a8702e6b62 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -4,6 +4,8 @@ ### 🛠 Breaking changes +- [SwiftUI] Use `fixedSize` modifier for Host `matchContents` ([#44642](https://github.com/expo/expo/pull/44642) by [@nishan](https://github.com/intergalacticspacehighway)) +- [jetpack-compose] Use intrinsic size for Host `matchContents` to match iOS ([#44642](https://github.com/expo/expo/pull/44642) by [@nishan](https://github.com/intergalacticspacehighway)) - [SwiftUI] `TextField`: removed `defaultValue`, added `text` prop backed by an `ObservableState` (from `useNativeState`), added worklet support for `onTextChange`, and renamed `onValueChange` → `onTextChange`. ([#44988](https://github.com/expo/expo/pull/44988) by [@nishan](https://github.com/intergalacticspacehighway)) - [iOS] Match `TextField` and `SecureField` API with SwiftUI: replaced `multiline`, `numberOfLines`, `allowNewlines` with `axis` prop. Moved `keyboardType`, `autocorrectionDisabled`, `onSubmit` from props to modifier registry. Renamed events `onValueChanged` → `onValueChange`, `onFocusChanged` → `onFocusChange`, `onSelectionChanged` → `onSelectionChange`. Removed hardcoded `fixedSize`. Added `LineLimitModifier` with `reservesSpace` and range support. Added `OnSubmitModifier` and `KeyboardTypeModifier`. ([#44549](https://github.com/expo/expo/pull/44549) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Match `TextField` API to native Compose: renamed `TextInput` to `TextField`/`OutlinedTextField`, replaced individual keyboard props with `keyboardOptions`/`keyboardActions` matching `KeyboardOptions`/`KeyboardActions`, added `enabled`, `readOnly`, `isError`, `singleLine`, `maxLines`, `minLines`, `shape`, `colors`. Added composable slots: `placeholder`, `leadingIcon`, `trailingIcon`, `prefix`, `suffix`, `supportingText`. Added imperative `focus()`/`blur()` and `onFocusChanged` event. ([#44545](https://github.com/expo/expo/pull/44545) by [@nishan](https://github.com/intergalacticspacehighway)) diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt index 5a9f00ebdf5ae1..b4069aa1da8816 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt @@ -157,21 +157,26 @@ internal class HostView(context: Context, appContext: AppContext) : ) { measurables, constraints -> val useViewportSizeMeasurement = props.useViewportSizeMeasurement.value - // When matchContents is used, constraints may have infinite maxWidth/maxHeight. - // Some components (like DatePicker, segmented buttons) use horizontal scrolling - // internally and crash with infinite constraints. Bound infinite values to screen size. + // useViewportSizeMeasurement: clamp Infinity/0 maxConstraints to the safe area so the + // content has a concrete size to fill. + // matchContents: pass through, so children measure + // at intrinsic size (the unbounded constraint comes from onMeasure's UNSPECIFIED). val boundedConstraints = Constraints( minWidth = constraints.minWidth, - maxWidth = when { - constraints.maxWidth == Constraints.Infinity -> safeWidthPx - useViewportSizeMeasurement && constraints.maxWidth == 0 -> safeWidthPx - else -> constraints.maxWidth + maxWidth = if (useViewportSizeMeasurement && + (constraints.maxWidth == Constraints.Infinity || constraints.maxWidth == 0) + ) { + safeWidthPx + } else { + constraints.maxWidth }, minHeight = constraints.minHeight, - maxHeight = when { - constraints.maxHeight == Constraints.Infinity -> safeHeightPx - useViewportSizeMeasurement && constraints.maxHeight == 0 -> safeHeightPx - else -> constraints.maxHeight + maxHeight = if (useViewportSizeMeasurement && + (constraints.maxHeight == Constraints.Infinity || constraints.maxHeight == 0) + ) { + safeHeightPx + } else { + constraints.maxHeight } ) val placeables = measurables.map { it.measure(boundedConstraints) } diff --git a/packages/expo-ui/ios/HostView.swift b/packages/expo-ui/ios/HostView.swift index 47c9ecb7ce422d..98d7bbdf723ee8 100644 --- a/packages/expo-ui/ios/HostView.swift +++ b/packages/expo-ui/ios/HostView.swift @@ -64,6 +64,7 @@ struct HostView: ExpoSwiftUI.View, ExpoSwiftUI.WithHostingView { return HostLayout { Children() } + .fixedSize(horizontal: props.matchContentsHorizontal, vertical: props.matchContentsVertical) .modifier(LayoutDirectionModifier(layoutDirection: layoutDirection)) .modifier(ColorSchemeModifier(colorScheme: props.colorScheme?.toColorScheme())) .modifier(GeometryChangeModifier(props: props)) @@ -72,6 +73,7 @@ struct HostView: ExpoSwiftUI.View, ExpoSwiftUI.WithHostingView { return ZStack(alignment: alignment) { Children() } + .fixedSize(horizontal: props.matchContentsHorizontal, vertical: props.matchContentsVertical) .modifier(LayoutDirectionModifier(layoutDirection: layoutDirection)) .modifier(ColorSchemeModifier(colorScheme: props.colorScheme?.toColorScheme())) .modifier(GeometryChangeModifier(props: props)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3794c6ce46a73f..f6b42cdc33d310 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2378,9 +2378,9 @@ importers: packages/@expo/inline-modules: dependencies: - expo: - specifier: workspace:* - version: link:../../expo + '@expo/config-plugins': + specifier: workspace:~55.0.6 + version: link:../config-plugins devDependencies: '@types/jest': specifier: ^29.2.1 @@ -2724,9 +2724,9 @@ importers: debug: specifier: ^4.3.1 version: 4.4.3 - expo: - specifier: workspace:* - version: link:../../expo + expo-modules-autolinking: + specifier: workspace:~55.0.8 + version: link:../../expo-modules-autolinking resolve-from: specifier: ^5.0.0 version: 5.0.0