Skip to content
Agentmart
Submit a tool

native-modules-and-new-architecture

Writes New Architecture native code in typed local Expo modules.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-native-modules-and-new-architecture

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Writes New Architecture native code in typed local Expo modules. Use when adding Swift or Kotlin, a local or Turbo module, codegen, or checking a library for New Architecture. Not for config plugins (expo-prebuild-and-config-plugins).

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/native-modules-and-new-architecture

What's inside

Skill name
native-modules-and-new-architecture
Description
Writes New Architecture native code in typed local Expo modules. Use when adding Swift or Kotlin, a local or Turbo module, codegen, or checking a library for New Architecture. Not for config plugins (expo-prebuild-and-config-plugins).
License
MIT
Instructions
215 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (69)

  • SKILL.md
  • shared.json · assets/
  • native-libraries.json · examples/
  • TaskChecksum.podspec · examples/turbo-module/
  • CMakeLists.txt · examples/turbo-module/android/
  • build.gradle · examples/turbo-module/android/
  • task-checksum-jni.cpp · examples/turbo-module/android/src/main/cpp/
  • NativeTaskChecksumModule.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
  • NativeTaskChecksumPackage.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
  • TaskChecksumJni.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
  • task-checksum.cpp · examples/turbo-module/cpp/
  • task-checksum.h · examples/turbo-module/cpp/
  • RCTNativeTaskChecksum.h · examples/turbo-module/ios/
  • RCTNativeTaskChecksum.mm · examples/turbo-module/ios/
  • package.json · examples/turbo-module/
  • NativeTaskChecksum.ts · examples/turbo-module/src/
  • wiring-the-port.md · examples/
  • decisions.md · references/
  • expo-modules-api.md · references/
  • library-compatibility.md · references/
  • testing-and-proof.md · references/
  • turbo-modules-and-codegen.md · references/
  • check-lib.mjs · scripts/
  • check-native-libraries.mjs · scripts/
  • check-native-modules.mjs · scripts/
  • native-scan.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • native-libraries.json · templates/contracts/
  • native-libraries.schema.json · templates/contracts/
  • build.gradle · templates/modules/time-change/android/
  • AndroidManifest.xml · templates/modules/time-change/android/src/main/
  • TimeChangeModule.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
  • TimeChangeReceiver.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
  • TimeChangeSystem.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
  • expo-module.config.json · templates/modules/time-change/
  • TimeChange.podspec · templates/modules/time-change/ios/
  • TimeChangeModule.swift · templates/modules/time-change/ios/
  • TimeChangeSystem.swift · templates/modules/time-change/ios/
  • fake-time-change.test.ts · templates/src/services/time-change/
  • fake-time-change.ts · templates/src/services/time-change/
  • native-time-change-adapter.test.ts · templates/src/services/time-change/
  • native-time-change-adapter.ts · templates/src/services/time-change/
  • time-change-port.ts · templates/src/services/time-change/
  • package.json · tests/bases/app/
  • task-list.ts · tests/bases/app/src/features/tasks/
  • tsconfig.json · tests/bases/app/
  • RNCMaskedView.podspec · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/
  • build.gradle · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/android/
  • package.json · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/
  • expo-module.config.json · tests/bases/libs/fixture-node-modules/expo-haptics/
  • ExpoHaptics.podspec · tests/bases/libs/fixture-node-modules/expo-haptics/ios/
  • package.json · tests/bases/libs/fixture-node-modules/expo-haptics/
  • package.json · tests/bases/libs/fixture-node-modules/expo-router/
  • expo-module.config.json · tests/bases/libs/fixture-node-modules/expo/
  • package.json · tests/bases/libs/fixture-node-modules/expo/
  • build.gradle · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/android/
  • package.json · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/
  • react-native-safe-area-context.podspec · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/
  • package.json · tests/bases/libs/fixture-node-modules/react-native/
  • …and 9 more

SKILL.md (first lines)

# Native modules and the New Architecture

Makes the app's own native code safe to own on iOS and Android: native code only where no package
fits, written with the Expo Modules API in a local module under `modules/<name>/`, reached through one
typed, lazy, optional binding in its adapter behind a port, with the binding type and the Swift and
Kotlin definitions proven equal; Turbo Modules only where shared C++ or publishing needs them; and
every third-party native library either native on the New Architecture or a recorded decision. Two
zero-dependency checkers prove it.

## Rules that must hold

1. **Native code comes last, and lives in a local Expo module.** First an Expo SDK package, then a
   library that is native on the New Architecture, then a config plugin; only then a local module made
   by `npx create-expo-module@latest --local`, in Swift and Kotlin with the Expo Modules API. No native
   source outside `modules/<name>/` (no inline modules, no edits to generated `ios/`/`android/`) and no
   legacy bridge code (`RCT_EXPORT_MODULE`, `ReactContextBaseJavaModule`, `@ReactMethod`). Why: SDK 55 <!-- stack-pin-ignore: the release that changed it -->
   and later run only on the New Architecture, legacy modules run only through interop layers React
   Native will remove, and native code outside the module is never built or reviewed.
   (`native-placement`, `legacy-native-api`)
2. **The module folder is complete.** `expo-module.config.json` declares each platform and lists every
   `Module` class (Swift class names, Kotlin full names); every listed class exists, extends `Module`
   and is `public` in Swift; `ios/*.podspec` depends on `ExpoModulesCore`; `android/build.gradle`
   applies `expo-module-gradle-plugin`. Why: autolinking registers only listed classes, and a missing
   piece fails one platform's build or leaves the module unreachable.
   (`module-config`, `module-class`, `module-build-files`)
3. **One JavaScript name per module.** `Name("TimeChange")` is written explicitly, is identical in
   Swift and Kotlin, and is unique in the app. Why: the name is the lookup key; inferred or differing
   names find the module on one platform only. (`module-name`)
4. **One binding per module, in its adapter, behind a port.** `requireOptionalNativeModule` and
   `requireNativeView` appear only in the module's adapter, `src/services/<concern>/native-<concern>-adapter.ts`
   (the checker accepts any `<library>-<concern>-adapter.ts(x)` in `src/services`, following
   architecture-and-boundaries), with a literal name a local module or spec defines; every module is
   bound, by exactly one adapter (platform variants `-adapter.ios.ts`/`.android.ts` count as one);
   `TurboModuleRegistry.get` appears only in a codegen spec (`Native<Name>.ts`); no
   `NativeModules`, `requireNativeComponent`, `NativeEventEmitter` or `UIManager`. The port, fake and
   composition root follow architecture-and-boundaries. Why: one line reaches native code, so one
   place guards it and everything above it runs in Jest on the fake; a second binding is a second,
   unguarded copy of the contract. (`binding-place`, `binding-unique`, `binding-name`, `codegen-spec`,
   `legacy-js-api`)
5. **The binding is lazy and optional.** The lookup runs in the factory's default parameter, never at
   module scope, and uses `requireOptionalNativeModule` for both kinds of module (for a Turbo Module it
   falls back to `TurboModuleRegistry.get`): a missing module becomes a typed `unavailable` failure.
   Why: the module is missing in Jest, on web, in a dev client or store build older than the module,
   and when an update reaches an older binary; the throwing forms crash at import there.
   (`binding-lazy`, `binding-optional`)
6. **The binding type is the cross-language contract.** `requireOptionalNativeModule<TimeChangeBinding>`
   names a type declared in the adapter that lists every native function, property and event by its
   native name, and nothing else; Swift, Kotlin and the type change in one commit. Why: the Expo Modules
   API has no cross-platform codegen (`expo-type-information` drafts a type from Swift only), so a
   renamed member compiles everywhere and fails at runtime. For a Turbo Module the codegen spec is the
   contract: bind `requireOptionalNativeModule<Spec>(...)` with `import type { Spec }` from its
   `Native<Name>.ts`; a hand-written type is compared with the spec. (`binding-type`, `contract-drift`)
7. **Same surface on iOS and Android, or a declared difference.** A member one platform lacks is
   optional in the binding type (`isAutomaticTimeEnabledAsync?()`) and the adapter maps its absence to
   `unsupported`; a one-platform module says so in `platforms`. Why: an undeclared gap is "is not a
   function" on one platform only, found by users. (`platform-parity`)
8. **Definitions stay thin, respect threads and release what they observe.** Each closure calls a plain
   helper that imports no Expo type; `Function`/`Property`/`Constant` only do cheap reads; UI work runs
   in `AsyncFunction(...).runOnQueue(.main)` (`Queues.MAIN`); OS observation starts in
   `OnStartObserving`, stops in `OnStopObserving` (same event, or the unnamed form) and `OnDestroy` in
   the Module itself, and every observer or receiver added is removed in the same file. Why: sync
   functions block the JavaScript thread, async ones default to a background queue, and a leaked
   observer keeps firing and keeps the module alive; a helper with a correct `stop()` still leaks when
   the Module never calls it. (`main-thread`, `observer-release`, `observer-stop`)
9. **Turbo Modules only for shared C++ or publishing, built as a local package.** `modules/<name>/` with
   `package.json` `codegenConfig` (`name`, `type`, a `jsSrcsDir` holding specs,
   `android.javaPackageName`), its podspec at the package root, specs named `Native<Name>.ts` declaring
   `export interface Spec extends TurboModule`; never `codegenConfig` in the app's `package.json`. Why:
   codegen reads only those files, autolinking looks for a React Native module's podspec only at its
   root, and CNG deletes app-level native code. (`codegen-config`, `codegen-spec`)
10. **Every native dependency runs on the New Architecture natively, or is a recorded decision.** A
    dependency without `expo-module.config.json`, `codegenConfig` or a Nitro marker has an entry in
    `contracts/native-libraries.json` (architecture, keep or replace, reason, evidence, `recheckBy`);
    entries stay true and are rechecked by their date. Why: such a library runs through the interop
    layer, whose limits and removal the app must plan for, not discover.
    (`library-legacy`, `library-entry`, `library-stale`, `library-recheck`)
11. **expo-doctor's React Native Directory check stays on.** No `enabled: false`; every exact-name
    `exclude` has a decision entry; no regex exclusions. Why: the directory check is the only automatic
    warning for unmaintained or New-Architecture-incompatible packages. (`doctor-check`)
12. **Proven on both platforms before done; a native change is a new binary.** Jest covers the fake and
    the adapter's mapping; `npx expo-modules-autolinking resolve` lists the module for apple and
    android; the Swift type-checks and the Kotlin helpers compile (testing-and-proof.md); a dev client
    on a named simulator and emulator exercises every member. Why: Fast Refresh and over-the-air
    updates never carry native code, and each layer catches what the others cannot.
13. **Stop and ask before** a new native module, a Turbo Module instead of an Expo module, keeping an
    interop library, an expo-doctor exclusion, or a `nativeSourceDirs` entry. Why: each binds the app
    for every later SDK and OS move.

## Workflow

1. **Decide whether native code is needed.** Read [references/decisions.md](references/decisions.md)
   sections 1 to 4; walk the order of reach. A native package: go to step 7. Ask the developer before
   writing a module (rule 13).
2. **Create the module.** From the app root:
   `EXPO_NONINTERACTIVE=1 npx create-expo-module@latest --local <name> --name <Pascal> --platform apple android --features Function AsyncFunction Event`.
   Delete its `src/` folder (the binding lives in the adapter). Shape it like
   `templates/modules/time-change/` (config, podspec, Gradle file, a thin `<Pascal>Module` per platform
   over a plain `<Pascal>System` helper). API details: [references/expo-modules-api.md](references/expo-modules-api.md).
3. **Write the port, fake and adapter.** Copy `templates/src/services/time-change/` as the pattern:
   `<concern>-port.ts` (Result failures, unsubscribe), `fake-<concern>.ts` + test first, then
   `native-<concern>-adapter.ts` with the binding type, the lazy optional binding and the error mapping,
   and its test against a stand-in native object. Wire it as
   [examples/wiring-the-port.md](examples/wiring-the-port.md) shows (architecture-and-boundaries owns the
   composition root).
4. **Need shared C++ or a module for non-Expo apps?** Read
   [references/turbo-modules-and-codegen.md](references/turbo-modules-and-codegen.md) and follow
   [examples/turbo-module/](examples/turbo-module/) instead of steps 2 and 3's module half.
5. **Check the contract:** `node ${CLAUDE_SKILL_DIR}/scripts/check-native-modules.mjs .`; fix every
   `FAIL` line and rerun.
6. **Prove it builds and links** ([references/testing-and-proof.md](references/testing-and-proof.md)):
   `npm test`, `npx expo-modules-autolinking resolve --platform apple --json` and `--platform android`,
   the Swift type-check, the Kotlin helper compile; then a dev client on a named iOS simulator and
   Android emulator (ios-simulator-build, android-emulator-build), every member on both, and the manual
   check for OS events.
7. **Check native libraries.** After `npm ci`:
   `node ${CLAUDE_SKILL_DIR}/scripts/check-native-libraries.mjs .` and `npx expo-doctor`. An `interop`
   library: replace it, wrap what you need in a module, or (after asking) record it in
   `contracts/native-libraries.json` from `templates/contracts/` ([examples/native-libraries.json](examples/native-libraries.json);
   [references/library-compatibility.md](references/library-compatibility.md)).

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (11 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

native-modules-and-new-architecture skill. Agent Mart. https://agentmart.dev/skills/zakariaf-native-modules-and-new-architecture (last updated 2026-10-24)