|
| 1 | +# CLAUDE.md — react-native-compressor |
| 2 | + |
| 3 | +This file is a project guide for Claude Code / AI agents working on `react-native-compressor`. It describes the repository layout, how the library is structured, how to build and test, common pitfalls, and the conventions used when making changes. |
| 4 | + |
| 5 | +## 1. Project overview |
| 6 | + |
| 7 | +`react-native-compressor` is a React Native module that compresses images, videos, and audio, creates video thumbnails, downloads remote media, and performs background file uploads. |
| 8 | + |
| 9 | +- **Language stack:** TypeScript (JS wrapper), Kotlin (Android), Swift/Objective-C++ (iOS). |
| 10 | +- **Module systems:** Supports both the legacy Native Modules bridge and the New Architecture TurboModules spec. The current `1.19.3` branch remains on TurboModules; `main` (2.x) has migrated to Nitro Modules (see §8). |
| 11 | +- **Package manager:** Yarn 4 with workspaces. The repository contains two example apps under `examples/`. |
| 12 | +- **Node engine:** >= 22.11.0. |
| 13 | + |
| 14 | +## 2. Repository layout |
| 15 | + |
| 16 | +```text |
| 17 | +react-native-compressor/ |
| 18 | +├── android/ # Android library (Kotlin, Gradle) |
| 19 | +├── ios/ # iOS library (Swift, Objective-C++) |
| 20 | +├── src/ # TypeScript source and TurboModule spec |
| 21 | +│ ├── Audio/ |
| 22 | +│ ├── Image/ |
| 23 | +│ ├── Video/ |
| 24 | +│ ├── Spec/ # TurboModule spec |
| 25 | +│ ├── expo-plugin/ |
| 26 | +│ ├── utils/ # Upload, download, helpers |
| 27 | +│ ├── Main.tsx # Module resolution (Turbo / legacy) |
| 28 | +│ ├── index.tsx # Public exports |
| 29 | +│ └── global.d.ts |
| 30 | +├── __tests__/ # Jest unit tests |
| 31 | +├── examples/ |
| 32 | +│ ├── bare/ # Bare React Native example |
| 33 | +│ └── expo/ # Expo example |
| 34 | +├── harness/ # react-native-harness configuration |
| 35 | +├── media/ # Test assets |
| 36 | +├── package.json |
| 37 | +├── CONTRIBUTING.md |
| 38 | +├── TRIAGE.md # Upstream issue audit |
| 39 | +└── README.md |
| 40 | +``` |
| 41 | + |
| 42 | +## 3. Public API |
| 43 | + |
| 44 | +The default export and named exports live in `src/index.tsx`: |
| 45 | + |
| 46 | +```ts |
| 47 | +import { |
| 48 | + Image, |
| 49 | + Video, |
| 50 | + Audio, |
| 51 | + backgroundUpload, |
| 52 | + cancelUpload, |
| 53 | + download, |
| 54 | + getDetails, |
| 55 | + uuidv4, |
| 56 | + generateFilePath, |
| 57 | + getRealPath, |
| 58 | + getVideoMetaData, |
| 59 | + getImageMetaData, |
| 60 | + createVideoThumbnail, |
| 61 | + clearCache, |
| 62 | + getFileSize, |
| 63 | + UploadType, |
| 64 | + UploaderHttpMethod, |
| 65 | +} from 'react-native-compressor'; |
| 66 | +``` |
| 67 | + |
| 68 | +Key behavioral rules: |
| 69 | + |
| 70 | +- `Image.compress(value, options?)` rejects empty `value` before calling native code and strips `data:image/...;base64,` headers. |
| 71 | +- `Video.compress(fileUrl, options?, onProgress?)` always generates an internal UUID, defaults `compressionMethod` to `'auto'` and `maxSize` to `640`, and supports `stripAudio`. |
| 72 | +- `Audio.compress(url, options?)` defaults to `{ quality: 'medium' }`. |
| 73 | +- `backgroundUpload(url, fileUrl, options, onProgress?, abortSignal?)` removes the `file://` prefix on Android and wires an `AbortSignal` to `cancelUpload`. |
| 74 | +- `download(fileUrl, progress?, progressDivider?)` also strips `file://` on Android. |
| 75 | + |
| 76 | +## 4. Native module resolution |
| 77 | + |
| 78 | +`src/Main.tsx` resolves the native module: |
| 79 | + |
| 80 | +```ts |
| 81 | +const isTurboModuleEnabled = global.__turboModuleProxy != null; |
| 82 | +const CompressorModule = isTurboModuleEnabled |
| 83 | + ? require('./Spec/NativeCompressor').default |
| 84 | + : NativeModules.Compressor; |
| 85 | +``` |
| 86 | + |
| 87 | +The TurboModule spec is in `src/Spec/NativeCompressor.ts`. Do **not** rename these methods without updating all native call sites. |
| 88 | + |
| 89 | +## 5. Development workflow |
| 90 | + |
| 91 | +Install dependencies: |
| 92 | + |
| 93 | +```sh |
| 94 | +yarn |
| 95 | +``` |
| 96 | + |
| 97 | +Run the examples: |
| 98 | + |
| 99 | +```sh |
| 100 | +yarn example:bare start |
| 101 | +yarn example:bare android |
| 102 | +yarn example:bare ios |
| 103 | +yarn example:expo start |
| 104 | +``` |
| 105 | + |
| 106 | +Code quality gates: |
| 107 | + |
| 108 | +```sh |
| 109 | +yarn typecheck # tsc --noEmit |
| 110 | +yarn lint # eslint "**/*.{js,ts,tsx}" |
| 111 | +yarn lint --fix # auto-fix formatting / lint issues |
| 112 | +yarn test # Jest unit tests |
| 113 | +yarn test:pr # test + typecheck + lint (CI gate) |
| 114 | +``` |
| 115 | + |
| 116 | +Clean build folders: |
| 117 | + |
| 118 | +```sh |
| 119 | +yarn clean |
| 120 | +``` |
| 121 | + |
| 122 | +Release (maintainers only): |
| 123 | + |
| 124 | +```sh |
| 125 | +yarn release |
| 126 | +``` |
| 127 | + |
| 128 | +## 6. Testing |
| 129 | + |
| 130 | +### Unit tests |
| 131 | + |
| 132 | +`__tests__/compressor.test.ts` mocks the native module and validates the JS wrapper contract. When you change option forwarding, event-emitter wiring, or the export surface, update this test. |
| 133 | + |
| 134 | +Run: |
| 135 | + |
| 136 | +```sh |
| 137 | +yarn test |
| 138 | +``` |
| 139 | + |
| 140 | +### Native harness tests |
| 141 | + |
| 142 | +For changes that affect compression/upload/download native behavior, run the harness tests on a real device or simulator: |
| 143 | + |
| 144 | +```sh |
| 145 | +yarn test:harness:android |
| 146 | +yarn test:harness:ios |
| 147 | +``` |
| 148 | + |
| 149 | +Harness config lives in `examples/bare/jest.harness.config.mjs`. The harness tests exercise actual media decoding and are the source of truth for native behavior. |
| 150 | + |
| 151 | +### Example apps |
| 152 | + |
| 153 | +Always test manually in `examples/bare` and `examples/expo` when touching: |
| 154 | + |
| 155 | +- Native Android/iOS code |
| 156 | +- The Expo plugin (`app.plugin.js`, `src/expo-plugin/`) |
| 157 | +- Upload/download progress wiring |
| 158 | + |
| 159 | +## 7. Git conventions |
| 160 | + |
| 161 | +- **Commit format:** Conventional Commits. |
| 162 | + - `fix:` bug fixes |
| 163 | + - `feat:` new features |
| 164 | + - `refactor:` code refactoring |
| 165 | + - `docs:` documentation changes |
| 166 | + - `test:` test changes |
| 167 | + - `chore:` tooling / CI |
| 168 | +- **Pre-commit hooks** (`lefthook.yml`) run ESLint and `tsc --noEmit` on staged files; `commit-msg` runs `commitlint`. |
| 169 | +- **PR template:** `.github/PULL_REQUEST_TEMPLATE.md` requires a Summary, Changelog entry, and Test Plan. |
| 170 | + |
| 171 | +Always run the PR gate before pushing: |
| 172 | + |
| 173 | +```sh |
| 174 | +yarn test:pr |
| 175 | +``` |
| 176 | + |
| 177 | +## 8. Branch strategy and the Nitro migration |
| 178 | + |
| 179 | +- `main` is on 2.x and has migrated to **Nitro Modules** (`feat: migrate react-native-compressor to Nitro Modules #401`). |
| 180 | +- The current working branch is `1.19.3`, which keeps the TurboModule / legacy bridge structure. |
| 181 | +- Fixes that land on `1.19.3` may need to be forward-ported to `main`/Nitro, and vice versa. Check both branches when resolving regressions. |
| 182 | + |
| 183 | +## 9. Platform-specific notes |
| 184 | + |
| 185 | +### Android |
| 186 | + |
| 187 | +- Source lives in `android/src/main/java/com/reactnativecompressor/`. |
| 188 | +- Uses Kotlin coroutines (`kotlinx-coroutines-core/android 1.6.4`). |
| 189 | +- Uses `org.mp4parser:isoparser:1.9.56` for MP4 container manipulation. |
| 190 | +- Uses `com.github.kaushik-naik:TAndroidLame` for audio encoding. |
| 191 | +- `android/build.gradle` enables `buildConfig` for AGP 8+ compatibility. |
| 192 | +- `file://` prefixes are stripped before passing paths to native methods (done in JS for download/upload). |
| 193 | + |
| 194 | +### iOS |
| 195 | + |
| 196 | +- Source lives in `ios/` and is organized into `Audio/`, `Image/`, `Video/`, and `Utils/`. |
| 197 | +- Entry point: `ios/Compressor.mm` with `CompressorManager.swift` coordinating work. |
| 198 | +- `AssetsLibrary` has been removed (iOS 26 / modern SDK support). |
| 199 | +- Guard against audio-only / missing video tracks; exports must fail explicitly rather than silently producing audio-only MP4s. |
| 200 | + |
| 201 | +## 10. Common pitfalls |
| 202 | + |
| 203 | +1. **Native method names.** The spec in `src/Spec/NativeCompressor.ts` must match the Android/iOS method names. Renames require three-way updates. |
| 204 | +2. **UUID wiring.** Progress events are keyed by UUID. If a native call does not receive the UUID, progress callbacks will not fire. |
| 205 | +3. **File URI normalization.** Android native APIs expect plain filesystem paths; `file://` stripping happens in `src/utils/Uploader.tsx` and `Downloader.tsx`. iOS generally accepts `file://` URIs. |
| 206 | +4. **Event listener cleanup.** Wrappers add `NativeEventEmitter` listeners in `try` and remove them in `finally`. Always preserve this pattern to avoid leaks. |
| 207 | +5. **Base64 images.** `Image.compress` strips the base64 data URL header before calling `image_compress`. Do not double-strip in native code. |
| 208 | +6. **Expo plugin changes.** If you change config-plugin behavior, test in `examples/expo` and verify `app.plugin.js` still resolves correctly. |
| 209 | +7. **New Architecture.** Builds with `newArchEnabled=true` generate code into `android/build/generated/source/codegen/java`. Clean builds are required when the spec changes. |
| 210 | +8. **Swift errors vs NSException.** Do not call `NSException.raise()` in iOS Swift code invoked by a TurboModule — it aborts the process and no JS catch can intercept it. Use Swift `throw` with a typed error; the calling Swift code wraps in `do/catch` and calls `reject`. This applies to all iOS image/video/audio compression handlers. |
| 211 | + |
| 212 | +## 11. What to do when touching specific areas |
| 213 | + |
| 214 | +| Area | Files to read / update | Validation | |
| 215 | +| --- | --- | --- | |
| 216 | +| Image compression | `src/Image/index.tsx`, `ios/Image/`, `android/src/.../Image/` | Unit test + bare example | |
| 217 | +| Video compression | `src/Video/index.tsx`, `ios/Video/`, `android/src/.../Video/` | Harness test + bare example | |
| 218 | +| Audio compression | `src/Audio/index.tsx`, `ios/Audio/`, `android/src/.../Audio/` | Bare example | |
| 219 | +| Upload / download | `src/utils/Uploader.tsx`, `src/utils/Downloader.tsx` | Unit test + harness test | |
| 220 | +| Thumbnails / metadata | `src/utils/index.tsx`, native utils | Unit test + bare example | |
| 221 | +| Expo plugin | `app.plugin.js`, `src/expo-plugin/` | `examples/expo` build | |
| 222 | +| TurboModule spec | `src/Spec/NativeCompressor.ts` | `yarn typecheck`, rebuild native apps | |
| 223 | +| CI / tooling | `.github/workflows/`, `package.json`, `lefthook.yml` | Push to a PR and inspect Actions | |
| 224 | + |
| 225 | +## 12. Useful commands |
| 226 | + |
| 227 | +```sh |
| 228 | +# Full PR gate |
| 229 | +yarn test:pr |
| 230 | + |
| 231 | +# Android build (debug) |
| 232 | +yarn build:android |
| 233 | + |
| 234 | +# iOS build (debug simulator) |
| 235 | +yarn build:ios |
| 236 | + |
| 237 | +# Native harness |
| 238 | +yarn test:harness:android |
| 239 | +yarn test:harness:ios |
| 240 | + |
| 241 | +# Clean everything |
| 242 | +yarn clean |
| 243 | +``` |
| 244 | + |
| 245 | +## 13. Release notes |
| 246 | + |
| 247 | +Published versions use `release-it` with the conventional-changelog plugin. The changelog is generated from commit messages, so write clear, scoped commits. |
| 248 | + |
| 249 | +--- |
| 250 | + |
| 251 | +When in doubt, keep changes minimal, add or update unit tests, and run `yarn test:pr` before asking for review. |
0 commit comments