0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2025年5月 React Native 開発環境

Last updated at Posted at 2025-04-29

TLDR

devcontainer

  • 開発環境は devcontainer で管理しているため、RN 開発でもそうしたい
  • iOS / Android のビルドは devcontainer では不可能
  • なので、Hermes はコンテナで、Android はホストで実行

arch.png

.devcontainer/devcontainer.json

{
  "name": "Node.js & TypeScript",
  "dockerComposeFile": "docker-compose/docker-compose.yml",
  "service": "devcontainer",
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

  "forwardPorts": [8081],

  "customizations": {
    "vscode": {
      "extensions": [
        "esbenp.prettier-vscode",
        "/tmp/vscode-react-native-1.14.0.vsix",
        "dbaeumer.vscode-eslint",
        "mike-co.import-sorter"
      ],
      "settings": {
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.formatOnSave": true,

        "editor.codeActionsOnSave": {
          "source.organizeImports": "never",
          "source.fixAll.eslint": "explicit"
        },
        "typescript.preferences.importModuleSpecifier": "relative"
      }
    }
  },

  "remoteUser": "node"
}

.devcontainer/docker-compose/docker-compose.yml

  • [注意] node_modules を volume にしているので、ホスト側でも npm install が必要
name: your_project
services:
  devcontainer:
    init: true
    build:
      context: devcontainer
    volumes:
      - ../../..:/workspaces:cached
      - node_modules:/workspaces/your_project/node_modules
    command: sleep infinity

volumes:
  node_modules:

.devcontainer/docker-compose/devcontainer/Dockerfile

FROM mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm
COPY vscode-react-native-1.14.0.vsix /tmp/

デバッグ

.vscode/launch.json

{
  "configurations": [
    {
      "name": "Attach Android Hermes",
      "request": "attach",
      "type": "reactnativedirect",
      "cwd": "${workspaceFolder}",
      "platform": "android"
    }
  ]
}

パス エイリアス

  • 自分のコードは src ディレクトリ以下で管理する
  • @/ で src ディレクトリが参照できるように、パスエイリアスを設定する
  • デフォルトで babel.config.js に指定されている module:metro-react-native-babel-preset だとうまく動かないので、@react-native/babel-preset に差し替える
    https://github.com/facebook/react-native/issues/50683#issuecomment-2807742468
  • ( パスエイリアス の設定がいちばん面倒くさい )

babel.config.cjs

module.exports = {
  presets: ['@react-native/babel-preset'],
  plugins: [
    'react-native-reanimated/plugin',
    [
      'module-resolver',
      {
        extensions: [
          '.ios.js',
          '.android.js',
          '.ios.jsx',
          '.android.jsx',
          '.js',
          '.jsx',
          '.json',
          '.ts',
          '.tsx',
        ],
        root: ['./src'],
        alias: {
          '@': './src',
        },
      },
    ],
  ],
};

tsconfig.json

{
  "extends": "@react-native/typescript-config/tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"]
    },
    "types": ["jest"]
  },
  "include": ["src"]
}

metro.config.cjs

const path = require('path');

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import('@react-native/metro-config').MetroConfig}
 */
const config = {
  watchFolders: [path.resolve(__dirname, 'src')],
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

import オーダー

  • import の並び順が、パスエイリアス込みで、良い感じになるように

.eslintrc.cjs

module.exports = {
  root: true,
  extends: '@react-native',
  plugins: ['import'],
  rules: {
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          ['parent', 'sibling', 'index'],
        ],
        pathGroups: [
          {
            pattern: '@/**',
            group: 'internal',
          },
        ],
        pathGroupsExcludedImportTypes: ['builtin'],
        alphabetize: {
          order: 'asc',
          caseInsensitive: true,
        },
        'newlines-between': 'always',
      },
    ],
  },
  settings: {
    'import/resolver': {
      typescript: {
        project: './tsconfig.json',
      },
    },
  },
};

jest

  • vitest は動かなかったので jest で
  • jest にも、パスエイリアスの設定が必要

jest.config.cjs

module.exports = {
  preset: 'react-native',
  transform: {
    '^.+\\.[jt]sx?$': 'babel-jest',
  },
  transformIgnorePatterns: [
    'node_modules/(?!(react-native|@react-native|@react-navigation|react-redux)/)',
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
  coverageDirectory: '<rootDir>/coverage',
  setupFiles: ['./jest.setup.cjs'],
};

jest.setup.cjs

/* global jest */
const mockAsyncStorage = require('@react-native-async-storage/async-storage/jest/async-storage-mock');
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);

参考

package.json

{
  "name": "your_project",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "react-native start",
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint \"src/**/*.{ts,tsx}\"",
    "format": "prettier \"src/**/*.{ts,tsx,json}\" --write",
    "fix": "npm run lint -- --fix && npm run format",
    "test": "jest --detectOpenHandles",
    "test:coverage": "jest --coverage"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^2.1.2",
    "@react-native-picker/picker": "^2.11.0",
    "@react-navigation/native": "^7.1.6",
    "@react-navigation/native-stack": "^7.3.10",
    "@reduxjs/toolkit": "^2.7.0",
    "axios": "^1.8.4",
    "babel-plugin-module-resolver": "^5.0.2",
    "i18next": "^25.0.1",
    "metro-react-native-babel-preset": "^0.77.0",
    "react": "^19.0.0",
    "react-i18next": "^15.5.1",
    "react-native": "^0.79.1",
    "react-native-reanimated": "^3.17.5",
    "react-native-safe-area-context": "^5.4.0",
    "react-native-screens": "^4.10.0",
    "react-redux": "^9.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@babel/preset-env": "^7.25.3",
    "@babel/runtime": "^7.25.0",
    "@react-native-community/cli": "18.0.0",
    "@react-native-community/cli-platform-android": "18.0.0",
    "@react-native-community/cli-platform-ios": "18.0.0",
    "@react-native/babel-preset": "0.79.1",
    "@react-native/eslint-config": "0.79.1",
    "@react-native/metro-config": "0.79.1",
    "@react-native/typescript-config": "0.79.1",
    "@testing-library/react-native": "^13.2.0",
    "@types/jest": "^29.5.14",
    "@types/react": "^19.0.0",
    "@typescript-eslint/eslint-plugin": "^7.18.0",
    "@typescript-eslint/parser": "^7.18.0",
    "babel-jest": "^29.7.0",
    "eslint": "^8.19.0",
    "eslint-import-resolver-typescript": "^4.3.4",
    "eslint-plugin-import": "^2.31.0",
    "eslint-plugin-react": "^7.37.5",
    "jest": "^29.7.0",
    "prettier": "2.8.8",
    "react-test-renderer": "^19.0.0",
    "typescript": "5.0.4"
  },
  "engines": {
    "node": ">=18"
  }
}

実行手順

  1. host 側 : npm install

  2. vscode 側 : npm start

  3. host 側 : npm run adroid

  4. vscode 側 : デバッグ開始 ( attach metro )

もし動かなくなったら

キャッシュ消す

npm start -- --reset-cache
cd android
./gradlew clean
cd ..
npm run adroid
cd ios
xcodebuild clean
cd ..
npm run ios
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?