npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@obitrain/react-native-image

v0.3.2

Published

Fast image component for React Native: Nitro view with Kingfisher (iOS) and Coil 3 (Android), decode-to-size, FastImage-compatible API

Readme

@obitrain/react-native-image

A fast image component for React Native, built as a Nitro view (react-native-nitro-modules) with Kingfisher on iOS and Coil 3 on Android. It exposes a FastImage-compatible API and adds what FastImage lacks: decode-to-size — every image is decoded at the view's pixel size (ImageIO CGImageSourceCreateThumbnailAtIndex / Coil size()), never at full resolution — plus prefetch() and transcode() APIs.

Requirements

  • React Native 0.87.x, New Architecture (Fabric), bare RN or Expo prebuild.
  • react-native-nitro-modules 0.36.x in the app (peer dependency >=0.35.0 <0.37.0).
  • iOS 15.1+, Xcode 16.4+ (tested on Xcode 26 / Swift 6.2), CocoaPods. Android minSdk 24, Kotlin 2.2+, NDK 27.

Installation

yarn add @obitrain/react-native-image   # or: npm install @obitrain/react-native-image
cd ios && pod install

Android autolinks. Kingfisher (~> 8.12) and Coil (3.4.0) are pulled in by the podspec / build.gradle; the package ships its generated Nitro code, so no codegen step is needed. You can also pin a release tag ("@obitrain/react-native-image": "Obitrain/obi-image#v0.1.1") — each GitHub release carries the same tarball.

Usage

import { Image, prefetch } from '@obitrain/react-native-image';

<Image source={{ uri }} resizeMode="cover" style={{ width: 70, height: 50, borderRadius: 10 }} onError={() => setFallback(true)} />
<Image source={require('./icon.png')} tintColor="red" resizeMode="contain" style={{ width: 30, height: 30 }} />
<Image source={{ uri }} style={styles.card}>
  <Pressable style={StyleSheet.absoluteFill} onPress={open} />   {/* children render above the image and are tappable */}
</Image>
<FlatList renderItem={({ item }) => <Image source={{ uri: item.uri }} recyclingKey={item.id} style={styles.thumb} />} />

await prefetch(nextScreen.map((r) => r.thumbUrl)); // warm the disk cache (iOS: memory too) before navigating

| Prop | Type | Notes | |---|---|---| | source | { uri: string } | number (require()) | Bundled assets resolve to http (Metro), file:// (iOS release) or a drawable (Android release). | | resizeMode | 'cover' (default) | 'contain' | 'center' | 'stretch' | Same names as FastImage / RN. | | style | StyleProp<ViewStyle> | Applied to the wrapper View; borderRadius clips. Give a numeric width/height so the decode size is known at first render. | | tintColor | ColorValue | Template-renders the image (icons). | | onLoad | () => void | Fires after a local, cached, or network image loads successfully. | | onError | () => void | Fires when the load fails (404, decode error, missing bundled asset). | | recyclingKey | string | Set to the row id in lists so a recycled cell never shows the previous row's image. | | children | ReactNode | Rendered as siblings above the native image view — overlays, buttons. | | ...ViewProps | | testID, accessibilityLabel, pointerEvents, … go to the wrapper View. |

prefetch(urls: string[]): Promise<void> — fetches into the cache; per-URL failures are ignored (they just stay uncached).

transcode(uri, { maxSize = 2048, quality = 0.85 }): Promise<TranscodedImage> — re-encodes any decodable image (HEIC, PNG, WebP, JPEG…) as a JPEG whose longest side is at most maxSize (never upscaled), for uploads to servers that only take JPEG. EXIF orientation is applied, metadata (incl. GPS) is dropped and transparency is flattened onto white. uri: file://, http(s)://, ph:// (iOS Photos), content:// (Android) or a require()d asset's Image.resolveAssetSource(...).uri. Resolves to { uri, width, height, size } with a file:// URI in the temporary/cache directory. HEIC needs Android 9+.

import { transcode } from '@obitrain/react-native-image';

const jpeg = await transcode(pickedPhoto.uri, { maxSize: 1024 });
upload({ uri: jpeg.uri, type: 'image/jpeg', name: 'image.jpg' });

Not supported (yet): priority, cacheControl, headers, defaultSource, onLoad, animated GIF playback on iOS (the first frame is shown), ph:// Photos URIs in <Image> (transcode() accepts them).

How it works

  • src/Image.tsx — JS wrapper with FastImage-shaped props. Owns require() resolution, points→pixels (PixelRatio), and the View{overflow:'hidden'} + absolute-fill native view + children layout that gives borderRadius clipping and tappable overlays for free. Wrapped in React.memo; onError is memoized and wrapped with Nitro's callback() (Nitro views require function props to be wrapped).
  • src/ImageView.nitro.ts — the Nitro view spec (ObitrainImageView); src/ImagePrefetcher.nitro.ts — the prefetch module.
  • ios/HybridImageView.swift — UIImageView + Kingfisher DownsamplingImageProcessor (pixel size, .scaleFactor(1), cover decodes with 1.25× headroom so the short side still fills). Originals go to disk (.cacheOriginalImage) so a new size never re-downloads; the downsampled bitmap is memory-only (custom CacheSerializer returning no data). kf.setImage cancels the previous task per view; prepareForRecycle resets everything.
  • android/.../HybridImageView.kt — AppCompatImageView + a library-owned Coil ImageLoader (ImageLoaders.kt: own disk cache dir, 10 % memory cache, allowRgb565) with size(w,h) + Precision.INEXACT. Coil disposes the previous request per view.

Toolchain notes (RN 0.87 + nitro 0.36) — read before touching the native side

  • react-native-nitro-modules / nitrogen are pinned to 0.36.5. nitro 0.37 would make the first two workarounds below unnecessary; bump both together and re-run yarn nitrogen.
  • RN 0.87 requires Fabric component views to set _props in their constructor; nitrogen 0.36.x does not emit that. scripts/patch-nitrogen.mjs patches the generated *Component.mm after every yarn nitrogen. Without it the app aborts on first mount: RCTViewComponentView subclasses ... must setup _props instance variable.
  • Xcode 26 / Swift 6.2: nitrogen 0.36 emits Bool(fromCxx:), rejected by Swift 6.2; the podspec script_phase rewrites it.
  • Never name a Nitro view ImageView: Fabric resolves that name to React Native's own RCTImageComponentView and silently renders core <Image> instead of yours. Hence ObitrainImageView.
  • Pin ndkVersion to the app's (getExtOrDefault("ndkVersion"), default 27.1.12297006). Built with a newer NDK (28), the .so needs __cxa_init_primary_exception, which the libc++_shared.so packaged by react-android/fbjni (NDK 27) lacks → UnsatisfiedLinkError: dlopen failed at launch, Release builds only.
  • Coil stays on 3.4.0 (Kotlin 2.3.10 metadata); 3.5+ is compiled with Kotlin 2.4 and cannot be consumed by a Kotlin 2.2 app.
  • Kingfisher: do not combine .cacheMemoryOnly with .cacheOriginalImage (fatal error in CacheCallbackCoordinator); the memory-only serializer is the safe equivalent. Do not add .backgroundDecode: ImageIO thumbnails are already decoded.
  • nitrogen/generated and src/generated are committed so consumers need no codegen step. Regenerate with yarn nitrogen after editing any *.nitro.ts, and commit the result.

Development

yarn                     # installs the library and the example workspace (nitro pinned to 0.36.5)
yarn nitrogen            # regenerate Nitro specs (+ RN 0.87 patch + view config module)
yarn typecheck && yarn lint
yarn example prebuild --clean           # generate native projects
yarn example start                      # Expo development server on port 8083
yarn example ios                        # or: yarn example android

example/ (Expo SDK 58, RN 0.88.0) has a Demo screen exercising the whole surface: decode-to-size, require() + tint, tappable overlay, onError, prefetch and a 200-row recycling list, and a Transcode screen (HEIC/WebP/PNG/https → JPEG at a chosen max size, with before/after sizes).

End-to-end tests

Detox specs in example/e2e/ tap every button of the example on Release builds (JS and assets bundled, no Metro); CI runs them on an iOS simulator and an Android emulator (e2e-ios, e2e-android).

brew tap wix/brew && brew install applesimutils   # iOS permissions
yarn example prebuild --clean
yarn example e2e:build:ios && yarn example e2e:ios            # iPhone 17 simulator (DETOX_IOS_DEVICE)
yarn example e2e:build:android && yarn example e2e:android    # Medium_Phone AVD (DETOX_AVD; DETOX_ANDROID_ARCH=x86_64 on Intel)

example/plugins/with-detox.js adds the Android wiring Detox needs (Detox AAR, DetoxTest, localhost cleartext for Release); iOS needs none. The bundled HEIC sample is photo-heic.bin: Android Release builds reject .heic drawables, so it ships as a raw resource instead.

Releasing

Merging to main never publishes. To release, open a release PR:

yarn release 0.2.0

It bumps package.json on a release/0.2.0 branch and opens a PR whose description (drafted from the commits since the last tag) becomes the release notes — edit it before merging. Merging the PR runs publish.yml: checks, npm publish through trusted publishing (no token to manage), tag v0.2.0 and the GitHub release with the tarball.

License

MIT

The example benchmark compares RN Image, FastImage, and obi-image with a fixed 10-second scroll sequence. It records mount-to-load-callback median/p95 times, failures, and incomplete rows, with up to 10 extra seconds for pending loads. Repeat cached URLs after a completed run to exercise caches; cache hits are not guaranteed. Recent comparisons remain available until the app reloads. These are end-to-end load times, not isolated decoder, memory, or frame-rate measurements.