mcpField
E
expo-config-setup

expo-config-setup

github.com/dengineproblem開発77 回インストール
リポジトリ

expo-config-setup について

expo-config-setup は、Expo プロジェクトの設定を支援するスキルです。静的・動的設定、プラットフォーム別オプション、ビルド設定を扱い、アプリ設定を行う開発者を対象としています。

主な機能

  1. 01静的設定と動的設定を使い分け
  2. 02iOS と Android の個別オプションを設定
  3. 03環境変数と環境別設定を処理
  4. 04EAS Build とプラグインを設定
  5. 05アセット、ディープリンク、OTA 更新を扱う

活用シーン

  1. 01複数環境向けに Expo アプリを設定
  2. 02iOS と Android のプラットフォーム設定を補完
  3. 03アプリのアセットやディープリンクを設定
  4. 04ビルド設定と一般的な問題を確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dengineproblem/agents-monorepo --skill expo-config-setup

インストール後、Claude Code で/expo-config-setupを入力して読み込まれたか確認します。

詳細

Expo 設定セットアップのエキスパート

最適な開発ビルドと本番ビルドを実現するために、app.json、app.config.js、プラットフォーム固有の設定を行うエキスパートです。

基本的な設定原則

静的設定と動的設定
  • 環境間で変更されない静的設定には app.json を使用します
  • 環境変数や条件付きロジックが必要な動的設定には app.config.js を使用します
  • 機密データを設定ファイルに直接記述しないでください。環境変数を使用します
プラットフォーム固有の設定
  • iOS と Android の両方のプラットフォームを明示的に設定します
  • 要件が異なる場合は、プラットフォーム固有のオーバーライドを使用します
  • 権限、機能、UI ガイドラインにおけるプラットフォーム間の違いを考慮します

必須のアプリ設定の構成

// app.config.js
export default {
  expo: {
    name: process.env.APP_NAME || "My App",
    slug: "my-app",
    version: "1.0.0",
    orientation: "portrait",
    icon: "./assets/icon.png",
    userInterfaceStyle: "automatic",
    splash: {
      image: "./assets/splash.png",
      resizeMode: "contain",
      backgroundColor: "#ffffff"
    },
    assetBundlePatterns: [
      "**/*"
    ],
    ios: {
      supportsTablet: true,
      bundleIdentifier: process.env.IOS_BUNDLE_ID || "com.company.myapp",
      buildNumber: process.env.IOS_BUILD_NUMBER || "1",
      infoPlist: {
        NSCameraUsageDescription: "This app uses the camera to take photos.",
        NSLocationWhenInUseUsageDescription: "This app uses location to provide location-based features."
      }
    },
    android: {
      adaptiveIcon: {
        foregroundImage: "./assets/adaptive-icon.png",
        backgroundColor: "#FFFFFF"
      },
      package: process.env.ANDROID_PACKAGE || "com.company.myapp",
      versionCode: parseInt(process.env.ANDROID_VERSION_CODE) || 1,
      permissions: [
        "android.permission.CAMERA",
        "android.permission.ACCESS_FINE_LOCATION"
      ]
    },
    web: {
      favicon: "./assets/favicon.png",
      bundler: "metro"
    }
  }
};

環境別の設定

// app.config.js with environment handling
const IS_DEV = process.env.APP_VARIANT === 'development';
const IS_PREVIEW = process.env.APP_VARIANT === 'preview';

const getAppName = () => {
  if (IS_DEV) return 'MyApp (Dev)';
  if (IS_PREVIEW) return 'MyApp (Preview)';
  return 'MyApp';
};

const getBundleId = () => {
  if (IS_DEV) return 'com.company.myapp.dev';
  if (IS_PREVIEW) return 'com.company.myapp.preview';
  return 'com.company.myapp';
};

export default {
  expo: {
    name: getAppName(),
    slug: IS_DEV ? 'myapp-dev' : IS_PREVIEW ? 'myapp-preview' : 'myapp',
    scheme: IS_DEV ? 'myapp-dev' : IS_PREVIEW ? 'myapp-preview' : 'myapp',
    version: process.env.APP_VERSION || '1.0.0',
    ios: {
      bundleIdentifier: getBundleId(),
    },
    android: {
      package: getBundleId(),
    },
    extra: {
      apiUrl: process.env.API_URL,
      environment: process.env.APP_VARIANT || 'production',
      eas: {
        projectId: process.env.EAS_PROJECT_ID
      }
    },
    updates: {
      url: `https://u.expo.dev/${process.env.EAS_PROJECT_ID}`
    },
    runtimeVersion: {
      policy: 'sdkVersion'
    }
  }
};

EAS ビルドの設定

// eas.json
{
  "cli": {
    "version": ">= 5.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "simulator": true
      },
      "env": {
        "APP_VARIANT": "development"
      }
    },
    "preview": {
      "distribution": "internal",
      "ios": {
        "resourceClass": "m-medium"
      },
      "android": {
        "buildType": "apk"
      },
      "env": {
        "APP_VARIANT": "preview"
      }
    },
    "production": {
      "ios": {
        "resourceClass": "m-medium"
      },
      "env": {
        "APP_VARIANT": "production"
      }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "[email protected]",
        "ascAppId": "1234567890",
        "appleTeamId": "ABCD1234"
      },
      "android": {
        "serviceAccountKeyPath": "./google-services.json",
        "track": "internal"
      }
    }
  }
}

プラグイン設定のベストプラクティス

// Advanced plugin configuration
export default {
  expo: {
    plugins: [
      "expo-font",
      "expo-router",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
          "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
          "recordAudioAndroid": true
        }
      ],
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location.",
          "locationAlwaysPermission": "Allow $(PRODUCT_NAME) to use your location.",
          "locationWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
        }
      ],
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#ffffff",
          "sounds": ["./assets/notification-sound.wav"],
          "mode": "production"
        }
      ],
      [
        "expo-build-properties",
        {
          "ios": {
            "deploymentTarget": "13.4",
            "useFrameworks": "static"
          },
          "android": {
            "compileSdkVersion": 34,
            "targetSdkVersion": 34,
            "buildToolsVersion": "34.0.0",
            "minSdkVersion": 23,
            "kotlinVersion": "1.9.0"
          }
        }
      ],
      [
        "expo-image-picker",
        {
          "photosPermission": "Allow $(PRODUCT_NAME) to access your photos",
          "cameraPermission": "Allow $(PRODUCT_NAME) to take pictures"
        }
      ]
    ]
  }
};

アセットとアイコンの設定

// Comprehensive asset configuration
export default {
  expo: {
    icon: "./assets/images/icon.png", // 1024x1024
    splash: {
      image: "./assets/images/splash.png", // 1284x2778 for iPhone 13 Pro Max
      resizeMode: "contain",
      backgroundColor: "#ffffff"
    },
    ios: {
      icon: "./assets/images/icon-ios.png", // iOS-specific icon if needed
      splash: {
        image: "./assets/images/splash-ios.png",
        resizeMode: "cover",
        backgroundColor: "#ffffff",
        tabletImage: "./assets/images/splash-tablet.png"
      }
    },
    android: {
      icon: "./assets/images/icon-android.png",
      adaptiveIcon: {
        foregroundImage: "./assets/images/adaptive-icon.png", // 1024x1024
        backgroundImage: "./assets/images/adaptive-icon-background.png",
        backgroundColor: "#FFFFFF"
      },
      splash: {
        image: "./assets/images/splash-android.png",
        resizeMode: "cover",
        backgroundColor: "#ffffff",
        mdpi: "./assets/images/splash-mdpi.png",    // 320x480
        hdpi: "./assets/images/splash-hdpi.png",    // 480x800
        xhdpi: "./assets/images/splash-xhdpi.png",  // 720x1280
        xxhdpi: "./assets/images/splash-xxhdpi.png", // 960x1600
        xxxhdpi: "./assets/images/splash-xxxhdpi.png" // 1280x1920
      }
    }
  }
};

ディープリンクとスキームの設定

// Complete deep linking setup
export default {
  expo: {
    scheme: "myapp",
    web: {
      bundler: "metro"
    },
    ios: {
      bundleIdentifier: "com.company.myapp",
      associatedDomains: [
        "applinks:myapp.com",
        "applinks:www.myapp.com"
      ]
    },
    android: {
      package: "com.company.myapp",
      intentFilters: [
        {
          action: "VIEW",
          autoVerify: true,
          data: [
            {
              scheme: "https",
              host: "myapp.com",
              pathPrefix: "/app"
            },
            {
              scheme: "https",
              host: "www.myapp.com",
              pathPrefix: "/app"
            }
          ],
          category: ["BROWSABLE", "DEFAULT"]
        },
        {
          action: "VIEW",
          data: [
            {
              scheme: "myapp"
            }
          ],
          category: ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
};

OTA アップデートの設定

// Over-the-air updates setup
export default {
  expo: {
    updates: {
      enabled: true,
      checkAutomatically: "ON_LOAD",
      fallbackToCacheTimeout: 30000,
      url: `https://u.expo.dev/${process.env.EAS_PROJECT_ID}`
    },
    runtimeVersion: {
      policy: "appVersion" // or "sdkVersion", "nativeVersion", "fingerprint"
    },
    // For custom update logic
    extra: {
      updateChannel: process.env.APP_VARIANT || 'production'
    }
  }
};
アプリ内のアップデートロジック
// App.js or updates hook
import * as Updates from 'expo-updates';

async function checkForUpdates() {
  if (__DEV__) return;

  try {
    const update = await Updates.checkForUpdateAsync();
    if (update.isAvailable) {
      await Updates.fetchUpdateAsync();
      await Updates.reloadAsync();
    }
  } catch (error) {
    console.error('Error checking for updates:', error);
  }
}

通知の設定

// Push notifications setup
export default {
  expo: {
    notification: {
      icon: "./assets/notification-icon.png", // 96x96, white on transparent
      color: "#3498db",
      androidMode: "default",
      androidCollapsedTitle: "#{unread_notifications} new notifications"
    },
    ios: {
      infoPlist: {
        UIBackgroundModes: ["remote-notification"]
      }
    },
    android: {
      googleServicesFile: "./google-services.json",
      useNextNotificationsApi: true
    },
    plugins: [
      [
        "expo-notifications",
        {
          icon: "./assets/notification-icon.png",
          color: "#3498db",
          sounds: ["./assets/sounds/notification.wav"],
          mode: "production"
        }
      ]
    ]
  }
};

セキュリティのベストプラクティス

// Secure configuration patterns
export default ({ config }) => {
  // Validate required env vars
  const requiredEnvVars = ['API_URL', 'EAS_PROJECT_ID'];
  for (const envVar of requiredEnvVars) {
    if (!process.env[envVar]) {
      console.warn(`Warning: ${envVar} is not set`);
    }
  }

  return {
    ...config,
    expo: {
      ...config.expo,
      // Never expose sensitive keys in extra
      extra: {
        apiUrl: process.env.API_URL,
        // Use EAS Secrets for sensitive values
        // NOT: apiKey: process.env.API_KEY
      },
      // Certificate pinning for production
      ios: {
        ...config.expo?.ios,
        infoPlist: {
          NSAppTransportSecurity: {
            NSAllowsArbitraryLoads: false,
            NSExceptionDomains: {
              "myapp.com": {
                NSExceptionRequiresForwardSecrecy: true,
                NSIncludesSubdomains: true
              }
            }
          }
        }
      }
    }
  };
};

よくある設定上の落とし穴

Missing Bundle Identifiers:
  problem: Build fails with "missing bundleIdentifier"
  solution: Always set ios.bundleIdentifier and android.package

Incorrect Asset Dimensions:
  problem: Icons/splash screens look blurry or cropped
  solution: Follow exact size requirements (icon: 1024x1024)

Version Code Issues:
  problem: Store rejects upload due to version code
  solution: Increment android.versionCode for each upload

Missing Permissions:
  problem: Feature crashes on first use
  solution: Declare all required permissions with descriptions

OTA Update Failures:
  problem: Updates not applying
  solution: Check runtimeVersion policy matches native builds

検証とテスト

# Validate configuration
npx expo doctor

# Check for common issues
npx expo-cli diagnostics

# Test deep links
# iOS
xcrun simctl openurl booted "myapp://path"
# Android
adb shell am start -a android.intent.action.VIEW -d "myapp://path"

# Preview configuration
npx expo config --type public
npx expo config --type introspect

ベストプラクティス

  1. 環境の分離 — dev/preview/prod 用に異なる設定を用意します
  2. 動的設定 — 環境変数には app.config.js を使用します
  3. EAS Secrets — 機密データは EAS Secrets に保存します
  4. バージョン管理 — CI/CD を通じてバージョンを自動管理します
  5. プラグインの監査 — プラグインを定期的に更新し、確認します
  6. ディープリンクのテスト — 両方のプラットフォームでテストします

コメント