init: 初始化模板项目

This commit is contained in:
2025-08-25 11:57:14 +08:00
commit aaa949bd7d
90 changed files with 20722 additions and 0 deletions

131
apps/expo/.gitignore vendored Normal file
View File

@@ -0,0 +1,131 @@
.expo
ios
android
# OSX
#
.DS_Store
# 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/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/*
web-build/
# @generated expo-cli sync-ee1e620bf946655de5f4a4ea0da0b18cabc4cf78
# The following patterns were generated by expo-cli
# OSX
#
.DS_Store
# 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/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/
web-build/
dist/
expo-env.d.ts
# @end expo-cli

38
apps/expo/app.json Normal file
View File

@@ -0,0 +1,38 @@
{
"expo": {
"name": "yourprojectsname",
"slug": "yourprojectsname",
"scheme": "yourprojectsname",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourprojectsname.app"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "com.yourprojectsname.app"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": ["expo-router", "expo-font"],
"experiments": {
"typedRoutes": true
}
}
}

48
apps/expo/app/_layout.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { useEffect } from 'react'
import { useColorScheme } from 'react-native'
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { useFonts } from 'expo-font'
import { SplashScreen, Stack } from 'expo-router'
import { Provider } from 'app/provider'
import { NativeToast } from '@my/ui/src/NativeToast'
export const unstable_settings = {
// Ensure that reloading on `/user` keeps a back button present.
initialRouteName: 'Home',
}
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync()
export default function App() {
const [interLoaded, interError] = useFonts({
Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
})
useEffect(() => {
if (interLoaded || interError) {
// Hide the splash screen after the fonts have loaded (or an error was returned) and the UI is ready.
SplashScreen.hideAsync()
}
}, [interLoaded, interError])
if (!interLoaded && !interError) {
return null
}
return <RootLayoutNav />
}
function RootLayoutNav() {
const colorScheme = useColorScheme()
return (
<Provider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack />
<NativeToast />
</ThemeProvider>
</Provider>
)
}

15
apps/expo/app/index.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { HomeScreen } from 'app/features/home/screen'
import { Stack } from 'expo-router'
export default function Screen() {
return (
<>
<Stack.Screen
options={{
title: 'Home',
}}
/>
<HomeScreen />
</>
)
}

7
apps/expo/app/types.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { config } from '@my/config'
export type Conf = typeof config
declare module '@my/ui' {
interface TamaguiCustomConfig extends Conf {}
}

View File

@@ -0,0 +1,21 @@
import { UserDetailScreen } from 'app/features/user/detail-screen'
import { Stack } from 'expo-router'
import { useParams } from 'solito/navigation'
export default function Screen() {
const { id } = useParams()
return (
<>
<Stack.Screen
options={{
title: 'User',
presentation: 'modal',
animation: 'slide_from_right',
gestureEnabled: true,
gestureDirection: 'horizontal',
}}
/>
<UserDetailScreen id={id as string} />
</>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
apps/expo/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
apps/expo/assets/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

35
apps/expo/babel.config.js Normal file
View File

@@ -0,0 +1,35 @@
module.exports = (api) => {
api.cache(true)
return {
presets: [['babel-preset-expo', { jsxRuntime: 'automatic' }]],
plugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
root: ['../..'],
alias: {
// define aliases to shorten the import paths
app: '../../packages/app',
'@my/ui': '../../packages/ui',
},
extensions: ['.js', '.jsx', '.tsx', '.ios.js', '.android.js'],
},
],
// if you want reanimated support
// 'react-native-reanimated/plugin',
...(process.env.EAS_BUILD_PLATFORM === 'android'
? []
: [
[
'@tamagui/babel-plugin',
{
components: ['@my/ui', 'tamagui'],
config: '../../packages/config/src/tamagui.config.ts',
logTimings: true,
disableExtraction: process.env.NODE_ENV === 'development',
},
],
]),
],
}
}

20
apps/expo/eas.json Normal file
View File

@@ -0,0 +1,20 @@
{
"build": {
"development": {
"distribution": "internal",
"android": {
"buildType": "apk"
},
"ios": {
"simulator": true,
"image": "latest"
}
},
"production": {
"distribution": "store",
"android": {
"buildType": "app-bundle"
}
}
}
}

7
apps/expo/index.js Normal file
View File

@@ -0,0 +1,7 @@
import 'setimmediate'
if (!global?.setImmediate) {
global.setImmediate = setTimeout
}
import 'expo-router/entry'

27
apps/expo/metro.config.js Normal file
View File

@@ -0,0 +1,27 @@
// Learn more https://docs.expo.dev/guides/monorepos
// Learn more https://docs.expo.io/guides/customizing-metro
/**
* @type {import('expo/metro-config')}
*/
const { getDefaultConfig } = require('@expo/metro-config')
const path = require('node:path')
const projectRoot = __dirname
const workspaceRoot = path.resolve(projectRoot, '../..')
const config = getDefaultConfig(projectRoot)
// 1. Watch all files within the monorepo
config.watchFolders = [workspaceRoot]
// 2. Let Metro know where to resolve packages and in what order
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
]
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true
config.transformer = { ...config.transformer, unstable_allowRequireContext: true }
config.transformer.minifierPath = require.resolve('metro-minify-terser')
module.exports = config

57
apps/expo/package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "expo-app",
"version": "1.0.0",
"main": "index.js",
"private": true,
"scripts": {
"start": "npx expo start -c",
"android": "npx expo run:android",
"ios": "yarn fix-xcode-env && npx expo run:ios",
"eject": "npx expo eject",
"fix-xcode-env": "node scripts/fix-xcode-env.mjs",
"prebuild": "yarn expo prebuild"
},
"dependencies": {
"@babel/runtime": "^7.26.0",
"@expo/config-plugins": "~10.0.0",
"@my/ui": "0.0.1",
"@react-navigation/bottom-tabs": "^7.3.12",
"@react-navigation/native": "^7.1.8",
"app": "0.0.0",
"babel-plugin-module-resolver": "^5.0.2",
"burnt": "^0.12.2",
"expo": "~53.0.8",
"expo-constants": "~17.1.6",
"expo-dev-client": "~5.1.8",
"expo-font": "~13.3.1",
"expo-linear-gradient": "~14.1.4",
"expo-linking": "~7.1.4",
"expo-router": "~5.0.6",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "~2.2.3",
"expo-updates": "~0.28.12",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.10.0",
"react-native-svg": "15.8.0",
"react-native-web": "^0.20.0"
},
"devDependencies": {
"@babel/core": "^7.24.6",
"@expo/metro-config": "~0.20.0",
"@tamagui/babel-plugin": "^1.132.18",
"metro-minify-terser": "^0.81.0",
"typescript": "~5.8.3"
},
"resolutions": {
"metro": "0.81.0",
"metro-resolver": "0.81.0"
},
"overrides": {
"metro": "0.81.0",
"metro-resolver": "0.81.0"
}
}

View File

@@ -0,0 +1,29 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { execSync } from 'node:child_process'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const iosDir = path.join(__dirname, '..', 'ios')
const xcodePath = path.join(iosDir, '.xcode.env.local')
async function main() {
try {
// Create ios directory if it doesn't exist
await fs.mkdir(iosDir, { recursive: true })
// Get the path to the Node binary
const nodePath = process.execPath
// Create or update the .xcode.env.local file
const content = `export NODE_BINARY=${nodePath}\n`
await fs.writeFile(xcodePath, content)
} catch (error) {
console.error('Error:', error.message)
process.exit(1)
}
}
main()

14
apps/expo/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base",
"include": [
"app/**/*.ts",
"app/**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
],
"compilerOptions": {
"composite": true,
"jsx": "react-jsx"
},
"references": []
}