Contributing

Internals & Contributing

How the package is built, how the pieces fit on disk, and how to make a change with confidence. If you want to contribute, start here.

For the day-to-day workflow commands (running the example app on a device, linting), see CONTRIBUTING.md. This document is the mental model behind those commands.


Repository layout

react-native-nitro-thumbnail/
β”œβ”€β”€ src/                          # TypeScript β€” the only code you import
β”‚   β”œβ”€β”€ index.ts                  # public createThumbnail (native platforms)
β”‚   β”œβ”€β”€ index.web.ts              # public createThumbnail (web, pure DOM)
β”‚   β”œβ”€β”€ types.ts                  # CreateThumbnailOptions, Thumbnail, error codes
β”‚   β”œβ”€β”€ errors.ts                 # ThumbnailError + toThumbnailError (code parsing)
β”‚   β”œβ”€β”€ native.ts                 # lazy HybridObject accessor
β”‚   └── specs/
β”‚       └── Thumbnail.nitro.ts    # ← the JS↔native contract (nitrogen input)
β”‚
β”œβ”€β”€ ios/
β”‚   β”œβ”€β”€ HybridThumbnail.swift     # AVFoundation implementation
β”‚   β”œβ”€β”€ ThumbnailEncoder.swift    # pure sizing/encoding/eviction helpers
β”‚   └── Tests/                    # XCTest for the pure helpers (not shipped)
β”‚
β”œβ”€β”€ android/src/
β”‚   β”œβ”€β”€ main/.../HybridThumbnail.kt       # MediaMetadataRetriever implementation
β”‚   β”œβ”€β”€ main/.../ThumbnailEncoderKt.kt    # pure sizing/encoding/eviction helpers
β”‚   β”œβ”€β”€ main/.../NitroThumbnailPackage.kt # autolinking package
β”‚   └── test/.../*.kt                     # JUnit for the pure helpers (not shipped)
β”‚
β”œβ”€β”€ __tests__/                    # Jest (node + jsdom) for the TS layer
β”œβ”€β”€ example/                      # runnable demo app (Yarn workspace)
β”œβ”€β”€ docs/                         # ← you are here
β”œβ”€β”€ nitrogen/                     # GENERATED by nitrogen (gitignored, built on prepare)
β”œβ”€β”€ lib/                          # GENERATED by bob (the published JS/types)
β”‚
β”œβ”€β”€ Thumbnail spec & config
β”‚   β”œβ”€β”€ nitro.json                # nitrogen config: module names, autolinking map
β”‚   β”œβ”€β”€ NitroThumbnail.podspec    # iOS pod definition
β”‚   └── package.json              # bob build targets, scripts, deps

The rule of thumb: src/ is the contract, ios/ + android/ implement it, nitrogen/ + lib/ are generated and never hand-edited.


The build pipeline

The package is built by react-native-builder-bob (β€œbob”), driven by yarn prepare (which npm/yarn run automatically on install and publish). Bob runs three targets in order:

  1. nitrogen (custom target) reads every *.nitro.ts spec and generates the native HybridThumbnailSpec protocol (Swift), abstract class (Kotlin), and the C++ JSI bindings into nitrogen/generated/. You implement the generated spec β€” you never write the bridge by hand.
  2. module transpiles src/ to ESM JavaScript in lib/module/.
  3. typescript emits .d.ts declarations to lib/typescript/.

package.json points consumers at the generated outputs:

"main":  "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",

When to re-run nitrogen

Run yarn nitrogen whenever you:

  • change src/specs/Thumbnail.nitro.ts (new method, new field, new type), or
  • build the project for the first time (the generated files are not committed).

The native code won’t compile against an out-of-date generated spec β€” that’s the point. The spec is the single source of truth, and codegen keeps both native sides honest.


Testing strategy

The library is tested at three levels, matching the three layers of the architecture. The principle throughout: extract the pure logic so it can be tested without a device.

LayerWhat’s testedHowWhere
TS public APIvalidation, defaults, error mapping, web pipelineJest (node + jsdom)__tests__/
iOS pure helperstargetSize, encode, filesToEvictXCTest / SwiftPMios/Tests/
Android pure helperstargetSize, mimeFor, encode, filesToEvictJUnitandroid/src/test/
End-to-endreal decode on real mediaexample appexample/
yarn test          # Jest β€” the TypeScript layer
yarn typecheck     # tsc, no emit
yarn lint          # eslint

The Jest suite covers createThumbnail validation/defaults, toThumbnailError parsing (including the [CODE] prefix and the funcName: wrapper), and the full web <video>/<canvas> path with jsdom fakes. The native helper tests cover the sizing math and LRU eviction with hand-built inputs β€” no video files required.

Why split pure helpers out (ThumbnailEncoder.swift, ThumbnailEncoderKt.kt, fitSize)? Because the interesting logic β€” aspect-fit math and eviction ordering β€” has nothing to do with AVFoundation or MediaMetadataRetriever. Pulling it into side-effect-free functions makes it fast and deterministic to test, and keeps the platform classes thin.


Making a change: worked examples

Add a new option (e.g. backgroundColor)

  1. Add it to CreateThumbnailOptions in src/types.ts and to NativeThumbnailOptions in src/specs/Thumbnail.nitro.ts.
  2. Normalize/default it in src/index.ts (and index.web.ts).
  3. yarn nitrogen to regenerate the native specs.
  4. Implement it in HybridThumbnail.swift, HybridThumbnail.kt, and the web path.
  5. Add Jest tests for the TS normalization; add native tests if it touches the pure helpers.
  6. Verify in the example app on a simulator/emulator.

Fix the sizing math

Edit only the pure helpers (targetSize / fitSize) and their unit tests β€” no device needed. The platform classes call into them unchanged.

Add an error code

  1. Add it to the ThumbnailErrorCode union and THUMBNAIL_ERROR_CODES array in src/types.ts (both β€” the array is the runtime allow-list toThumbnailError checks against).
  2. Throw it from native via the err("NEW_CODE", …) helper, or from the web/TS path via new ThumbnailError('NEW_CODE', …).
  3. Add a Jest test asserting it round-trips.
  4. Document it in the Error Handling guide and the API reference.

Coding conventions

  • Validate in TypeScript, act dumbly in native. Don’t push input validation into Swift/Kotlin β€” the native side should receive a complete, normalized struct. (See architecture β†’ design principles.)
  • Keep pure logic pure. Sizing/eviction/encoding decisions go in the *Encoder* helpers, free of I/O, so they stay unit-testable.
  • One code path per error. Every failure maps to exactly one ThumbnailErrorCode; encode it at the throw site with the err() helper.
  • Match the surrounding style. Prettier + ESLint configs are in the repo; run yarn lint --fix before committing.

Contributing checklist

  • yarn to install (uses Yarn 4 workspaces β€” not npm)
  • yarn nitrogen if you touched a *.nitro.ts spec
  • yarn test, yarn typecheck, yarn lint all green
  • New behavior has tests (pure helpers where possible)
  • Verified in the example app if native code changed
  • Docs updated (API reference / relevant guide)
  • Small, focused PR β€” discuss API/implementation changes in an issue first

Friendly reminder: the code of conduct applies to all interactions. New contributors are genuinely welcome β€” a good first PR is fixing or extending a doc you found confusing while reading these.