프론트엔드 환경 분리하기

황토소금·2024년 8월 25일

TIL

목록 보기
23/49

🎥 프롤로그

앱을 플레이 스토어에 심사를 걸어두고 마저 작업을 이어서 하려고 했다. 그러다가 문득 생각이 든 것이 현재 프론트엔드에서는 firebase console에서 프로젝트가 onestep-dev 하나밖에 없어서 개발 중에도 모든 이벤트 로그가 다 찍히고 있었다. 그래서 이번 기회에 프론트엔드에서 사용하는 툴들을 환경에 맞게 구분해보려고 한다.

목표
1. 환경을 development, staging, production으로 구분한다.
2. 툴들에서도 환경에 맞게 프로젝트를 구분한다.
3. apk 혹은 aab로 빌드했을 때 환경 변수가 잘 설정되었는지 확인할 수 있다.

🎬 #1 환경을 development, staging, production으로 구분한다.

이전에 환경 변수를 설정할 때 대략적으로 설정해두었다.
app.config.js에서 APP_MODE라는 환경 변수로 development 혹은 production를 구분했고 그것에 따라 sentry environment와 api 서버 주소를 구분했다.
APP_MODE는 터미널 커멘드에서 직접 주입하거나 package.json에 커멘드를 등록해두고 사용했었다.
그리고 빌드할 땐 eas.json에서 profile에 따라 expo 환경변수를 설정해두었다. 환경변수 관련 예전 글🔗

이제 staging 환경도 추가해볼 것

// eas.json

{
  "cli": {
    "version": ">= 10.0.2"
  },
  "build": {
    "staging": {
      "distribution": "internal",
      "channel": "staging",
      "android": {
        "applicationArchivePath": "android/app/build/outputs/apk/release/app-release.apk",
        "buildType": "apk"
      },
      "env": {
        "EXPO_PUBLIC_APP_MODE": "staging"
      }
    },
    "production": {
      "channel": "production",
      "node": "20.15.0",
      "env": {
        "EXPO_PUBLIC_APP_MODE": "production"
      }
    }
  },
  "submit": {
    "production": {}
  }
}

eas build를 할 때엔 기능 다 완성하고 실기기에서 잘 동작하는지 확인할 때라 development환경이 필요하지 않아서 뺐다.
staging일 땐 빠르게 설치하고 확인해보기 위해 apk로 빌드하기로 했다.

// app.config.js
let Config = {
  BASE_URL: '',
  SENTRY_MODE: '',
};

switch (process.env.APP_MODE || process.env.EXPO_PUBLIC_APP_MODE) {
  case 'production':
    Config.BASE_URL = '우리 서버 url';
    Config.SENTRY_MODE = 'production';
    break;

  case 'staging':
    Config.BASE_URL = 'https://dev.우리 서버 url';
    Config.SENTRY_MODE = 'staging';
    break;

  case 'development':
    Config.BASE_URL = 'https://dev.우리 서버 url';
    Config.SENTRY_MODE = 'development';
    break;

  case 'local':
    Config.BASE_URL = 'http://10.0.2.2:8000';
    Config.SENTRY_MODE = 'development';
    break;

  default:
    throw new Error('Invalid APP_MODE');
}
//...

그리고 app.config.js에서 환경에 따라 api 서버 url과 sentry 환경을 설정하도록 했다.
그리고 가끔 dev 서버가 죽는 경우도 있어서 로컬 서버 케이스도 만들었다.

🎬 #2 툴들에서도 환경에 맞게 프로젝트를 구분한다.

  1. Sentry

development 환경일 때 :
Sentry를 꺼두고 싶다. 개발할 땐 에러가 잘 잡히기 때문에 Sentry로 수집해야할 이유가 없다고 생각한다.

staging 환경일 때 :
발생하는 모든 에러를 수집했으면 좋겠다.

production 환경일 때 :
발생하는 모든 에러를 수집하기는 너무 많은 것 같아 samplerate를 조금 줄여보려고 한다. 하지만 아직 사용자가 매우 적기 때문에 1.0을 유지하되 문제를 파악하기 힘들 정도로 에러가 쌓인다면 그때 대응해도 좋을 것 같다.

//constants/env.js
//...
export const env = {
  BASE_URL: getEnv('BASE_URL'),
  SENTRY_MODE: getEnv('SENTRY_MODE'),
};

export const getSentryConfig = () => {
  switch (env.SENTRY_MODE) {
    case 'production':
      return {
        tracesSampleRate: 1.0,
        _experiments: {
          profilesSampleRate: 1.0,
          replaysSessionSampleRate: 1.0,
          replaysOnErrorSampleRate: 1.0,
        },
      };
    case 'staging':
      return {
        tracesSampleRate: 1.0,
        _experiments: {
          profilesSampleRate: 1.0,
          replaysSessionSampleRate: 1.0,
          replaysOnErrorSampleRate: 1.0,
        },
      };
  }
};
//...


//app/_layout.jsx
//...
import { env, getSentryConfig } from '@/constants/env';

const SENTRY_MODE = env.SENTRY_MODE;

Sentry.init({
  enabled: ['staging', 'production'].includes(SENTRY_MODE)
  environment: SENTRY_MODE,
  dsn: 'dsn 주소',
  ...getSentryConfig(),
  integrations: [
    Sentry.mobileReplayIntegration({
      maskAllText: true,
      maskAllImages: true,
    }),
  ],
});
//...

이런 식으로 SENTRY_MODE가 staging이나 production일 때만 sentry를 enable하게 하고 설정값은 env.js에서 getSentryConfig라는 함수로 받아오게 만들었다.
staing이나 production일때만 sentry가 enable되니까 getSentryConfig에서 다른 케이스는 고려하지 않았다.
그리고 env.js 파일 안에서 바로 SENTRY_MODE 값을 읽어와서 혹시나 오타가 난다거나 하는 경우를 막아보려고 해보았다!

  1. firebase console
//app.config.js
//...
let googleServiceJson = null; //default

switch (process.env.APP_MODE || process.env.EXPO_PUBLIC_APP_MODE) {
  case 'production':
    Config.BASE_URL = '우리 서버 url';
    Config.SENTRY_MODE = 'production';
    googleServiceJson =
      process.env.GOOGLE_SERVICES_PRODUCTION_JSON ||
      './google-services.production.json';
    break;

  case 'staging':
    Config.BASE_URL = '우리 서버 url';
    Config.SENTRY_MODE = 'staging';
    googleServiceJson =
      process.env.GOOGLE_SERVICES_STAGING_JSON ||
      './google-services.staging.json';
    break;

  case 'development':
    Config.BASE_URL = '우리 서버 url';
    Config.SENTRY_MODE = 'development';
    break;

  case 'local':
    Config.BASE_URL = 'http://10.0.2.2:8000';
    Config.SENTRY_MODE = 'development';
    break;

  default:
    throw new Error('Invalid APP_MODE');
}
//...

우선 firebase에서 staging, production, development환경에 맞게 프로젝트를 새로 파고 그거에 맞는 google services json을 eas secrets에 등록해두었다. 그래서 eas build할 때 eas.json에 설정된 환경 변수에 따라서 secrets에서 불러올 수 있게 했다. 그리고 로컬에서 확인할 때를 위해 프로젝트 루트 경로에도 json 파일을 넣어두고 gitignore에 설정해두었다.

그리고 development일 때는 firebase로 사용자 로그를 수집할 이유가 없어서
let googleServiceJson = null로 해두고

//app.config.js
//...
const expoConfig = {
  expo: {
  //...
  android: {
      adaptiveIcon: {
        foregroundImage: './assets/adaptive-icon.png',
        backgroundColor: '#ffffff',
      },
      package: '패키지 이름',
      versionCode: 1,
    },
  //...
  }

if (googleServiceJson) {
  expoConfig.expo.android.googleServicesFile = googleServiceJson;
}

module.exports = expoConfig;

이렇게 production이나 staging 환경이라서 googleServiceJson 값이 생겼을 때만 expoConfig.expo.android에 값을 넣어주고 exports하는 식으로 해보았다.

🎬 #3 apk 혹은 aab로 빌드했을 때 환경 변수가 잘 설정되었는지 확인할 수 있다.

에뮬레이터로 실행할 때 확인할 방법은 있는데 실기기에서도 잘 연결되었는가 확인할 방법이 찾아보아도 잘 나오지 않는다. 다들 어떻게 하는 걸까

일단 에뮬레이터에선...

  1. sentry
    sdk가 initialize된 직후 실행할 콜백 함수를 전달하는 onReady옵션이 있었는데 나중에 적용해봐야겠다. 지금은 원래 목록에 없던 staging 환경과 production 환경이 sentry 목록에 생겨서 잘 연결된 걸 확인했기 때문.
    나중에 문제가 생기면 그때 다시 해볼 것.

  2. firebase analytics
    firebase console에는 로그 값 반영이 몇시간 후였나 매우 늦게 반영되기 때문에 디버그 뷰로 확인하면 될 것 같다. 디버그뷰🔗
    이때 안드로이드의 경우

adb shell setprop debug.firebase.analytics.app PACKAGE_NAME

이 명령어에서 PACKAGE_NAME은 com.으로 시작하는 그 패키지 이름이다.

에필로그

expo에서 CNG(Continuous Native Generation)에 대해 조금 더 알게되었다. 지금까지 문서에서 몇번 보았지만 뭘 하는 건지 몰랐는데 이번에 app.config.js를 고쳐보면서 알게된 것은

  1. prebuild를 하면 android나 ios 폴더가 생기는데 app.config.js의 내용을 참고해서 폴더를 생성한다. 그래서 기존에 native 폴더에서 직접 무언가를 수정해야했던 일들 대신 app.config.js에 어떤 다른 툴들의 설정을 넣어두면 반영되는 거라고 이해했다.
  2. react native가 업데이트되면서 native 폴더도 수정해야 할 일이 생기는데 이때 간편하게 수정할 수 있다는 장점이 있다.
  3. 그런데 native 폴더를 직접 건드리면 그 이후부턴 eas prebuild로 native 폴더를 업데이트하는게 불가능하다고 한다. 예전에 android 폴더에서 파일 몇개를 수정했었는데 그때 eas prebuild를 하니 내 git status is dirty?라고 하던가 명령어가 동작하지 않았었는데 이 이유였던 것 같다.
  4. 그래서 어떤 툴을 도입하기 위해 native 폴더를 건드릴 일이 있으면 해당 툴에서 app.config.js에서 설정할 수 있게 해주는지 찾아보아야 할 것 같다.
  5. 그 외에 native 폴더를 수정해야하는 자잘한 이유들도 생길텐데 그땐 어떻게 해야하는지 궁금하다.

레퍼런스
1 2 3 4

profile
안녕하세요, 반갑습니다.

0개의 댓글