Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,15 @@ tsconfig.tsbuildinfo

# Claude Code local settings
.claude/settings.local.json

# React Native SwiftPM
# Build-time symlink to Xcode's generated Swift interop headers
.spm-derived-headers
.build/
.swiftpm/
**/*.xcodeproj/.spm-injected.json
# Public-header trees staged (as symlinks) by the autolinking plugin at
# `react-native spm add` time — machine-generated, never committed
**/nitrogen/generated/include/
**/nitrogen/generated/include-bridge/
packages/react-native-nitro-modules/include/
6 changes: 4 additions & 2 deletions example/ios/NitroExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
PODFILE_DIR = "$(SRCROOT)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
Expand Down Expand Up @@ -615,7 +616,8 @@
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
PODFILE_DIR = "$(SRCROOT)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_OBJC_INTEROP_MODE = objcxx;
Expand Down
18 changes: 9 additions & 9 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@
"@react-navigation/native": "^7.2.5",
"deep-equal": "^2.2.3",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native": "0.87.0",
"react-native-nitro-test": "*",
"react-native-nitro-test-external": "*",
"react-native-nitro-modules": "*",
"react-native-safe-area-context": "^5.8.0",
"react-native-screens": "^4.25.2"
"react-native-screens": "^4.27.0"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/preset-env": "^7.29.7",
"@babel/runtime": "^7.29.7",
"@react-native-community/cli": "20.1.3",
"@react-native-community/cli-platform-android": "20.1.3",
"@react-native-community/cli-platform-ios": "20.1.3",
"@react-native/babel-preset": "0.85.3",
"@react-native/eslint-config": "0.85.3",
"@react-native/metro-config": "0.85.3",
"@react-native/typescript-config": "0.85.3",
"@react-native-community/cli": "20.2.0",
"@react-native-community/cli-platform-android": "20.2.0",
"@react-native-community/cli-platform-ios": "20.2.0",
"@react-native/babel-preset": "0.87.0",
"@react-native/eslint-config": "0.87.0",
"@react-native/metro-config": "0.87.0",
"@react-native/typescript-config": "0.87.0",
"@react-native-harness/platform-android": "1.4.1",
"@react-native-harness/platform-apple": "1.4.1",
"@types/deep-equal": "^1.0.4",
Expand Down
11 changes: 10 additions & 1 deletion example/src/screens/HybridObjectTestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ import {
} from 'react-native-nitro-test'
import { getTests, type TestRunner } from '../getTests'
import { logPrototypeChain } from '../logPrototypeChain'
import SegmentedControl from '@react-native-segmented-control/segmented-control'
import SegmentedControlNative, {
type SegmentedControlProps,
} from '@react-native-segmented-control/segmented-control'
import { NitroModules } from 'react-native-nitro-modules'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useColors } from '../useColors'
import { TestCase, TestState } from '../components/TestCase'
import { KeyboardDismissBackground } from '../components/KeyboardDismissBackground'

// react-native 0.87 dropped `NativeMethods` from its public types, which breaks
// this (unmaintained) package's `Constructor<NativeMethods> & typeof Component`
// intersection — the resulting class loses its `props`. Its own props type is
// unaffected, so re-type the default export as a plain component.
const SegmentedControl =
SegmentedControlNative as unknown as React.ComponentType<SegmentedControlProps>

logPrototypeChain(HybridChild)
console.log(HybridBase.baseValue)
console.log(HybridChild.baseValue)
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^9.39.4",
"@jamesacarr/eslint-formatter-github-actions": "^0.2.0",
"@react-native/eslint-config": "0.85.3",
"@react-native/jest-preset": "0.85.3",
"@react-native/eslint-config": "0.87.0",
"@react-native/jest-preset": "0.87.0",
"@release-it-plugins/workspaces": "^5.0.3",
"@release-it/bumper": "^7.0.5",
"@release-it/conventional-changelog": "^10.0.6",
Expand All @@ -53,7 +53,7 @@
"jest": "^29.7.0",
"prettier": "^3.8.3",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native": "0.87.0",
"release-it": "^19.2.4",
"typescript": "~6.0.3",
"typescript-eslint": "^8.60.0"
Expand Down
44 changes: 43 additions & 1 deletion packages/nitrogen/src/autolinking/createIOSAutolinking.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
import fs from 'fs'
import path from 'path'
import type { Autolinking } from './Autolinking.js'
import { createHybridObjectIntializer } from './ios/createHybridObjectInitializer.js'
import { createPackageSwift } from './ios/createPackageSwift.js'
import { createPodspecRubyExtension } from './ios/createPodspecRubyExtension.js'
import { createSwiftCxxBridge } from './ios/createSwiftCxxBridge.js'
import { createSwiftUmbrellaHeader } from './ios/createSwiftUmbrellaHeader.js'
import { getSpmPackageName } from './ios/getSpmPackageName.js'

interface IOSAutolinking extends Autolinking {}

export function createIOSAutolinking(): IOSAutolinking {
/**
* Deletes a previously generated `Package.swift` when one can no longer be
* generated (e.g. the package has no readable `package.json`).
*
* Nitrogen's own dangling-file cleanup only covers `nitrogen/generated/`, and
* `Package.swift` lives at the PACKAGE ROOT — so without this it would linger
* and keep React Native's SwiftPM autolinking treating the module as a
* self-managed package.
*
* Only removes files carrying nitrogen's generated-by marker: a hand-authored
* `Package.swift` (a library shipping its own SwiftPM support) is never
* touched.
*/
function removeGeneratedPackageSwift(): void {
const manifest = path.join(process.cwd(), 'Package.swift')
try {
if (!fs.existsSync(manifest)) return
const content = fs.readFileSync(manifest, 'utf8')
if (!content.includes('This file was generated by nitrogen')) return
fs.rmSync(manifest)
} catch {
// Best-effort: a stale manifest is not worth failing codegen over.
}
}

export function createIOSAutolinking(outputDirectory: string): IOSAutolinking {
const podspecExtension = createPodspecRubyExtension()
const swiftCxxBridge = createSwiftCxxBridge()
const swiftUmbrellaHeader = createSwiftUmbrellaHeader()
const hybridObjectInitializer = createHybridObjectIntializer()
// React Native SwiftPM (Preview) support. Purely additive — CocoaPods apps
// never read Package.swift — and needs no configuration: the package
// identity is derived from the npm package name exactly as React Native's
// autolinker derives it.
const spmPackageName = getSpmPackageName(process.cwd())
const packageSwift =
spmPackageName != null
? createPackageSwift(spmPackageName, outputDirectory)
: []
// Also covers the case where a manifest cannot be expressed for this output
// directory - see `createPackageSwift`.
if (packageSwift.length === 0) removeGeneratedPackageSwift()

return {
platform: 'ios',
Expand All @@ -19,6 +60,7 @@ export function createIOSAutolinking(): IOSAutolinking {
...swiftCxxBridge,
swiftUmbrellaHeader,
...hybridObjectInitializer,
...packageSwift,
],
}
}
Loading