feat: use expo module to write native component/module (#2675)

* init

Signed-off-by: Innei <tukon479@gmail.com>

* test uikit

Signed-off-by: Innei <tukon479@gmail.com>

* uitable

Signed-off-by: Innei <tukon479@gmail.com>

* test webview

Signed-off-by: Innei <tukon479@gmail.com>

* shared webview

Signed-off-by: Innei <tukon479@gmail.com>

* refactor(shared-webview): Simplify WebView height management

Signed-off-by: Innei <tukon479@gmail.com>

* fix: add url prop

Signed-off-by: Innei <tukon479@gmail.com>

* rename

Signed-off-by: Innei <tukon479@gmail.com>

* feat: native module

Signed-off-by: Innei <tukon479@gmail.com>

* cleanup

Signed-off-by: Innei <tukon479@gmail.com>

* fix: export

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-02-02 23:55:05 +08:00 committed by GitHub
parent 86ed6d4eed
commit e1bf881e85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 3263 additions and 343 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ vite.config.*.mjs
apps/renderer/dev-dist
tsconfig.tsbuildinfo
apps/mobile/ios
apps/mobile/android

View File

@ -74,5 +74,7 @@
"i18n-ally.displayLanguage": "en",
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{namespaces}/{locale}.json"
"i18n-ally.pathMatcher": "{namespaces}/{locale}.json",
"lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB",
"lldb.launch.expressions": "native"
}

View File

@ -36,5 +36,3 @@ yarn-error.*
*.tsbuildinfo
app-example
ios
android

View File

@ -5,6 +5,17 @@ const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro
const config = getDefaultConfig(__dirname, { isCSSEnabled: true })
config.resolver.sourceExts.push("sql")
config.resolver.extraNodeModules = {
"follow-native": "./native",
}
config.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
})
module.exports = wrapWithReanimatedMetroConfig(
withNativeWind(config, { input: "./src/global.css" }),
)

View File

@ -0,0 +1,5 @@
module.exports = {
root: true,
extends: ["universe/native", "universe/web"],
ignorePatterns: ["build"],
}

57
apps/mobile/native/.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
# OSX
#
.DS_Store
# VSCode
.vscode/
jsconfig.json
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml
android/app/libs
android/keystores/debug.keystore
# Cocoapods
#
example/ios/Pods
# Ruby
example/vendor/
# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# Expo
.expo/*

View File

@ -0,0 +1,14 @@
# Exclude all top-level hidden directories by convention
/.*/
# Exclude tarballs generated by `npm pack`
/*.tgz
__mocks__
__tests__
/babel.config.js
/android/src/androidTest/
/android/src/test/
/android/build/
/example/

View File

@ -0,0 +1,32 @@
# follow-native
Nope
# API documentation
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/follow-native/)
- [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/follow-native/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects &mdash; it is likely to be included in an upcoming Expo SDK release.
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npm install follow-native
```
### Configure for Android
### Configure for iOS
Run `npx pod-install` after installing the npm package.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

View File

@ -0,0 +1,43 @@
apply plugin: 'com.android.library'
group = 'expo.modules.follownative'
version = '0.1.0'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useExpoPublishing()
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
// Most of the time, you may like to manage the Android SDK versions yourself.
def useManagedAndroidSdkVersions = false
if (useManagedAndroidSdkVersions) {
useDefaultAndroidSdkVersions()
} else {
buildscript {
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
}
project.android {
compileSdkVersion safeExtGet("compileSdkVersion", 34)
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
}
}
}
android {
namespace "expo.modules.follownative"
defaultConfig {
versionCode 1
versionName "0.1.0"
}
lintOptions {
abortOnError false
}
}

View File

@ -0,0 +1,2 @@
<manifest>
</manifest>

View File

@ -0,0 +1,50 @@
package expo.modules.follownative
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.net.URL
class FollowNativeModule : Module() {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('FollowNative')` in JavaScript.
Name("FollowNative")
// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
Constants(
"PI" to Math.PI
)
// Defines event names that the module can send to JavaScript.
Events("onChange")
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
Function("hello") {
"Hello world! 👋"
}
// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on the different thread than the JavaScript runtime runs on.
AsyncFunction("setValueAsync") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
}
// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(FollowNativeView::class) {
// Defines a setter for the `url` prop.
Prop("url") { view: FollowNativeView, url: URL ->
view.webView.loadUrl(url.toString())
}
// Defines an event that the view can send to JavaScript.
Events("onLoad")
}
}
}

View File

@ -0,0 +1,30 @@
package expo.modules.follownative
import android.content.Context
import android.webkit.WebView
import android.webkit.WebViewClient
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.views.ExpoView
class FollowNativeView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
// Creates and initializes an event dispatcher for the `onLoad` event.
// The name of the event is inferred from the value and needs to match the event name defined in the module.
private val onLoad by EventDispatcher()
// Defines a WebView that will be used as the root subview.
internal val webView = WebView(context).apply {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
// Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript.
onLoad(mapOf("url" to url))
}
}
}
init {
// Adds the WebView to the view hierarchy.
addView(webView)
}
}

44
apps/mobile/native/example/.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
# Expo
.expo
dist/
web-build/
ios
android

View File

@ -0,0 +1 @@
node-linker=hoisted

View File

@ -0,0 +1,12 @@
import { SharedWebView } from "follow-native"
import { ScrollView, View } from "react-native"
export default function App() {
return (
<ScrollView>
<SharedWebView url="https://innei.in" />
<View style={{ height: 1000, backgroundColor: "red" }} />
</ScrollView>
)
}

View File

@ -0,0 +1,30 @@
{
"expo": {
"name": "follow-native-example",
"slug": "follow-native-example",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "expo.modules.follownative.example"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "expo.modules.follownative.example"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true)
return {
presets: ["babel-preset-expo"],
}
}

View File

@ -0,0 +1,8 @@
import { registerRootComponent } from "expo"
import App from "./App"
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App)

View File

@ -0,0 +1,34 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config")
const path = require("node:path")
const config = getDefaultConfig(__dirname)
// npm v7+ will install ../node_modules/react and ../node_modules/react-native because of peerDependencies.
// To prevent the incompatible react-native between ./node_modules/react-native and ../node_modules/react-native,
// excludes the one from the parent folder when bundling.
config.resolver.blockList = [
...Array.from(config.resolver.blockList ?? []),
new RegExp(path.resolve("..", "node_modules", "react")),
new RegExp(path.resolve("..", "node_modules", "react-native")),
]
config.resolver.nodeModulesPaths = [
path.resolve(__dirname, "./node_modules"),
path.resolve(__dirname, "../node_modules"),
]
config.resolver.extraNodeModules = {
"follow-native": "..",
}
config.watchFolders = [path.resolve(__dirname, "..")]
config.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
})
module.exports = config

View File

@ -0,0 +1,27 @@
{
"name": "follow-native-example",
"version": "1.0.0",
"private": true,
"main": "index.ts",
"scripts": {
"android": "expo run:android",
"ios": "expo run:ios",
"start": "expo start",
"web": "expo start --web"
},
"dependencies": {
"expo": "^52.0.0",
"react": "18.3.1",
"react-native": "0.76.6"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~18.3.12",
"typescript": "^5.3.3"
},
"expo": {
"autolinking": {
"nativeModulesDir": ".."
}
}
}

View File

@ -0,0 +1,10 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"follow-native": ["../src/index"],
"follow-native/*": ["../src/*"]
}
}
}

View File

@ -0,0 +1,20 @@
const createConfigAsync = require("@expo/webpack-config")
const path = require("node:path")
module.exports = async (env, argv) => {
const config = await createConfigAsync(
{
...env,
babel: {
dangerouslyAddModulePathsToTranspile: ["follow-native"],
},
},
argv,
)
config.resolve.modules = [
path.resolve(__dirname, "./node_modules"),
path.resolve(__dirname, "../node_modules"),
]
return config
}

View File

@ -0,0 +1,9 @@
{
"platforms": ["apple", "android"],
"apple": {
"modules": ["SharedWebViewModule"]
},
"android": {
"modules": []
}
}

View File

@ -0,0 +1,58 @@
// https://github.com/expo/expo/blob/main/packages/expo-modules-core/ios/Core/Views/SwiftUI/AutoSizingStack.swift#L18
import SwiftUI
import ExpoModulesCore
extension ExpoSwiftUI {
public struct AxisSet: OptionSet {
public init(rawValue: Int) {
self.rawValue = rawValue
}
public let rawValue: Int
public static let horizontal = AxisSet(rawValue: 1 << 0)
public static let vertical = AxisSet(rawValue: 1 << 1)
public static let both: AxisSet = [.horizontal, .vertical]
}
public struct AutoSizingStack<Content: SwiftUI.View>: SwiftUI.View {
let content: Content
let proxy: ShadowNodeProxy
let axis: AxisSet
public init(shadowNodeProxy: ShadowNodeProxy, axis: AxisSet = .both, @ViewBuilder _ content: () -> Content) {
self.proxy = shadowNodeProxy
self.content = content()
self.axis = axis
}
public var body: some SwiftUI.View {
if #available(iOS 16.0, tvOS 16.0, *) {
content.fixedSize(horizontal: axis.contains(.horizontal), vertical: axis.contains(.vertical))
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: {
let width = axis.contains(.horizontal) ? $0.width : ShadowNodeProxy.UNDEFINED_SIZE
let height = axis.contains(.vertical) ? $0.height : ShadowNodeProxy.UNDEFINED_SIZE
let size = CGSize(width: width, height: height)
proxy.setViewSize?(size)
}
} else {
// TODO: throw a warning
content.onAppear(perform: {
log.warn("AutoSizingStack is not supported on iOS/tvOS < 16.0")
})
}
}
}
}
extension ExpoSwiftUI {
open class ShadowNodeProxy: ObservableObject, Record {
public required init() {}
// We use Double.nan to mark a dimension as unset. This value is passed to https://github.com/facebook/yoga/blob/49ee855f99fb67079c24d507a4ea1b6d80fa2ebf/yoga/style/StyleLength.h#L33
static let UNDEFINED_SIZE = Double.nan
public var setViewSize: ((CGSize) -> Void)?
}
}

View File

@ -0,0 +1,29 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'FollowNative'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '15.1',
:tvos => '15.1'
}
s.swift_version = '5.4'
s.source = { git: 'https://github.com/Innei/follow-native' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
s.dependency 'SnapKit', '~> 5.7.0'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
}
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
end

View File

@ -0,0 +1,64 @@
import Combine
import ExpoModulesCore
import SwiftUI
import WebKit
class WebViewView: ExpoView {
private var cancellable: AnyCancellable?
private let rctView = ExpoModulesCore.RCTView(frame: .zero)
required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)
addSubview(rctView)
#if DEBUG
rctView.borderStyle = .solid
rctView.borderWidth = 1
rctView.borderColor = .tintColor
#endif
rctView.addSubview(SharedWebViewModule.sharedWebView!)
clipsToBounds = true
cancellable = WebViewManager.state.$contentHeight
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.layoutSubviews()
}
}
deinit {
cancellable?.cancel()
}
private let onContentHeightChange = ExpoModulesCore.EventDispatcher()
override func layoutSubviews() {
let rect = CGRect(
x: bounds.origin.x,
y: bounds.origin.y,
width: bounds.width,
height: WebViewManager.state.contentHeight
)
SharedWebViewModule.sharedWebView!.frame = rect
frame = rect
rctView.frame = rect
onContentHeightChange(["height": Float(rect.height)])
}
}
struct WebViewComponent: UIViewRepresentable {
let webView: WKWebView
func makeUIView(context: Context) -> WKWebView {
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
}

View File

@ -0,0 +1,41 @@
//
// File.swift
//
// Created by Innei on 2025/1/29.
//
import ExpoModulesCore
import WebKit
public class SharedWebViewModule: Module {
public static var sharedWebView: WKWebView? {
WebViewManager.shared
}
public func definition() -> ModuleDefinition {
Name("FOSharedWebView")
Function("preload") { (url: String) in
DispatchQueue.main.async {
guard let webUrl = URL(string: url) else { return }
let request = URLRequest(url: webUrl)
WebViewManager.shared.load(request)
}
}
View(WebViewView.self) {
Events("onContentHeightChange")
Prop("url") { (_: UIView, urlString: String) in
if let webView = SharedWebViewModule.sharedWebView {
if let url = URL(string: urlString) {
if url == webView.url {
return
}
webView.load(URLRequest(url: url))
}
}
}
}
}
}

View File

@ -0,0 +1,116 @@
//
// WebViewManager.swift
// FollowNative
//
// Created by Innei on 2025/1/31.
//
import Combine
import ExpoModulesCore
import WebKit
enum WebViewManager {
static let state = WebViewState()
static private(set) var shared: WKWebView = {
let configuration = WKWebViewConfiguration()
let css = """
:root { overflow: hidden !important; }
body {
overflow-y: visible !important;
position: absolute !important;
width: 100% !important;
height: auto !important;
-webkit-overflow-scrolling: touch !important;
}
"""
let script = WKUserScript(
source: """
var style = document.createElement('style');
style.textContent = '\(css)';
document.head.appendChild(style);
""",
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
configuration.userContentController.addUserScript(script)
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.scrollView.isScrollEnabled = false
#if DEBUG
if #available(iOS 16.4, *) {
webView.isInspectable = true
}
#endif
setupWebView(webView)
return webView
}()
private static func setupWebView(_ webView: WKWebView) {
let delegate = WebViewDelegate(state: state)
webView.navigationDelegate = delegate
let script = WKUserScript(
source: """
window.addEventListener('load', function() {
window.webkit.messageHandlers.contentHeight.postMessage(document.documentElement.scrollHeight);
});
const observer = new ResizeObserver(function() {
window.webkit.messageHandlers.contentHeight.postMessage(document.documentElement.scrollHeight);
});
observer.observe(document.documentElement);
""",
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.addUserScript(script)
webView.configuration.userContentController.add(delegate, name: "contentHeight")
}
static func resetWebView() {
let newWebView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
setupWebView(newWebView)
shared = newWebView
}
}
// MARK: - WebViewState
private class WebViewDelegate: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
private let state: WebViewState
init(state: WebViewState) {
self.state = state
super.init()
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "contentHeight", let height = message.body as? CGFloat {
DispatchQueue.main.async {
self.state.contentHeight = height
}
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.scrollHeight") { (height, error) in
if let height = height as? CGFloat {
DispatchQueue.main.async {
self.state.contentHeight = height
}
}
}
}
}

View File

@ -0,0 +1,12 @@
//
// WebViewState.swift
// FollowNative
//
// Created by Innei on 2025/1/31.
//
import Combine
class WebViewState: ObservableObject {
@Published var contentHeight: CGFloat = UIWindow().bounds.height
}

View File

@ -0,0 +1,41 @@
{
"name": "follow-native",
"version": "0.1.0",
"description": "Nope",
"author": "Innei <tukon479@gmail.com> (https://github.com/Innei)",
"license": "MIT",
"homepage": "https://github.com/Innei/follow-native#readme",
"bugs": {
"url": "https://github.com/Innei/follow-native/issues"
},
"keywords": [
"react-native",
"expo",
"follow-native",
"FollowNative"
],
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"//prepare": "expo-module prepare",
"build": "expo-module build",
"clean": "expo-module clean",
"expo-module": "expo-module",
"lint": "expo-module lint",
"open:android": "open -a \"Android Studio\" example/android",
"open:ios": "xed example/ios",
"prepublishOnly": "expo-module prepublishOnly"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
},
"dependencies": {},
"devDependencies": {
"@types/react": "~18.3.12",
"expo": "~52.0.0",
"expo-module-scripts": "^4.0.3",
"react-native": "0.76.0"
}
}

View File

@ -0,0 +1,24 @@
import { requireNativeView } from "expo"
import type * as React from "react"
import { useState } from "react"
import { View } from "react-native"
const NativeView: React.ComponentType<{
onContentHeightChange?: (e: { nativeEvent: { height: number } }) => void
url: string
}> = requireNativeView("FOSharedWebView")
export default function SharedWebView({ url }: { url: string }) {
const [contentHeight, setContentHeight] = useState(0)
return (
<View style={{ height: contentHeight }}>
<NativeView
onContentHeightChange={(e) => {
setContentHeight(e.nativeEvent.height)
}}
url={url}
/>
</View>
)
}

View File

@ -0,0 +1,10 @@
import type { NativeModule } from "expo"
import { requireNativeModule } from "expo-modules-core"
export { default as SharedWebView } from "./SharedWebView"
declare class ISharedWebViewModule extends NativeModule {
preload(url: string): void
}
export const SharedWebViewModule = requireNativeModule<ISharedWebViewModule>("FOSharedWebView")

View File

@ -0,0 +1,9 @@
// @generated by expo-module-scripts
{
"extends": "expo-module-scripts/tsconfig.base",
"compilerOptions": {
"outDir": "./build"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
}

View File

@ -119,6 +119,9 @@
"react-native-safe-area-context@~5.1.0",
"react-native-webview@~13.12.5"
]
},
"autolinking": {
"nativeModulesDir": "./native"
}
}
}

View File

@ -5,7 +5,9 @@
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"follow-native": ["./native/src/index.ts"],
"follow-native/*": ["./native/*"]
},
"jsx": "preserve"
},

View File

@ -96,7 +96,7 @@ export default defineConfig(
"package-json-extend": packageJsonExtend,
},
rules: {
"package-json-extend/ensure-package-version": "error",
"package-json-extend/ensure-package-version": "warn",
"package-json-extend/no-duplicate-package": "error",
},
},

View File

@ -148,8 +148,10 @@
"overrides": {
"@types/react": "npm:@types/react@^18.3.12",
"@types/react-dom": "npm:@types/react-dom@^18.3.1",
"expo-modules-core": "npm:expo-modules-core@2.2.0",
"is-core-module": "npm:@nolyfill/is-core-module@^1",
"isarray": "npm:@nolyfill/isarray@^1",
"react-native": "npm:react-native@0.77.0",
"unist-util-visit-parents": "5.1.3",
"vfile": "5.3.7"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,6 @@
packages:
- "apps/*"
- "packages/*"
- "apps/mobile/native"
- "!**/example"
- "!**/example/*"