Files
pick-lunch/APPS_IN_TOSS_STARTER.md

6.5 KiB

Apps in Toss Starter

새 프로젝트를 시작할 때 기준으로 쓰는 최소 설정이다.

목표

  • RN 0.84.0 타깃 번들이 포함된 .ait 생성
  • 불필요한 Granite 플러그인 없이 Apps in Toss 기준으로 안정적으로 시작

기본 원칙

  • build는 반드시 ait build
  • granite.config.ts에는 target: '0.84.0' 명시
  • appsInToss({...}) 중심의 최소 구성 유지
  • router(), hermes() 같은 추가 Granite 플러그인은 꼭 필요한 경우에만 사용

시작 순서

1. 프로젝트 생성 후 의존성 설치

프로젝트 루트에서:

npm install

2. package.json 설정

아래 형태로 맞춘다.

{
  "name": "my-app",
  "private": true,
  "scripts": {
    "dev": "granite dev",
    "build": "ait build",
    "deploy": "ait deploy",
    "check:build": "./check-apps-in-toss-build.sh"
  },
  "dependencies": {
    "@apps-in-toss/framework": "^2.4.1",
    "@granite-js/native": "1.0.10",
    "@granite-js/react-native": "1.0.10",
    "brick-module": "0.5.1",
    "react": "19.2.3",
    "react-native": "0.84.0"
  },
  "devDependencies": {
    "@types/react": "19.2.0",
    "babel-preset-granite": "1.0.10",
    "typescript": "^5.8.3"
  }
}

3. babel.config.js 추가

루트에 아래 파일을 반드시 둔다.

module.exports = {
  presets: ['babel-preset-granite'],
};

이 파일이 없으면 react-native 패키지 내부 구문을 Metro가 잘못 파싱해서 Missing semicolon 에러가 날 수 있다.

4. index.js 엔트리 설정

루트 index.js는 아래처럼 시작한다.

const { register } = require('@granite-js/react-native');

register(require('./src/_app').default);

그냥 require('./src/_app')만 하면 Granite 개발 엔트리포인트인 shared가 등록되지 않아 "shared" has not been registered 에러가 날 수 있다.

5. granite.config.ts 설정

아래처럼 시작한다.

import { appsInToss } from '@apps-in-toss/framework/plugins';
import { defineConfig } from '@granite-js/react-native/config';

export default defineConfig({
  appName: 'my-app',
  scheme: 'intoss',
  plugins: [
    appsInToss({
      target: '0.84.0',
      brand: {
        displayName: '내 앱 이름',
        primaryColor: '#0064FF',
        icon: 'https://static.toss.im/appsintoss/your-icon.png',
      },
      navigationBar: {
        withBackButton: true,
        withHomeButton: true,
      },
      permissions: [],
    }),
  ],
});

6. src/pages/_404.tsx 추가

파일 기반 라우터가 fallback 페이지를 요구하므로 아래 파일을 기본으로 넣는다.

import { StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from '@granite-js/native/react-native-safe-area-context';

export default function NotFoundPage() {
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.container}>
        <Text style={styles.title}>Page not found</Text>
        <Text style={styles.description}>
          요청한 화면을 찾을  없습니다.
        </Text>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#F4F7FB',
  },
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    paddingHorizontal: 24,
  },
  title: {
    color: '#111827',
    fontSize: 24,
    fontWeight: '700',
    marginBottom: 8,
  },
  description: {
    color: '#4B5563',
    fontSize: 15,
    textAlign: 'center',
  },
});

권장하지 않는 시작 방식

아래는 이유 없으면 넣지 않는다.

router()
hermes()

이 조합은 프로젝트에 따라 Apps in Toss 패키징 결과를 꼬이게 만들 수 있다.

개발 명령어

개발 서버:

npm run dev

빌드:

npm run build

배포:

npm run deploy

검증:

npm run check:build

빌드 후 반드시 확인할 것

1. 빌드 로그 확인

아래 같은 로그가 보여야 한다.

[1/2] Built for RN 0.84.0
[2/2] Built for RN 0.72.6

2. .ait 안 번들 확인

아래 명령으로 확인:

python3 - <<'PY'
import zipfile
path='my-app.ait'
with zipfile.ZipFile(path) as z:
    for name in z.namelist():
        if name.startswith('bundle.') and name.endswith('.js'):
            print(name)
PY

정상 예시:

bundle.ios.0_84_0.js
bundle.android.0_84_0.js
bundle.ios.0_72_6.js
bundle.android.0_72_6.js

중요한 건 최소한 아래 두 파일이 포함되는 것이다.

  • bundle.ios.0_84_0.js
  • bundle.android.0_84_0.js

문제 생겼을 때 체크리스트

지원하지 않는 번들이에요. 최신 SDK를 사용해주세요가 뜨면

순서대로 확인:

  1. granite.config.tstarget: '0.84.0'가 있는지
  2. package.jsonbuildait build인지
  3. .ait 내부에 bundle.*.0_84_0.js가 실제로 들어 있는지
  4. 예전 .ait 파일을 업로드한 건 아닌지
  5. 불필요한 Granite 플러그인을 추가하지 않았는지

새 프로젝트 시작용 빠른 체크

프로젝트 준비 후 아래만 실행하면 된다.

npm install
npm run build

개발 실행 전에는 아래 파일들이 있는지 먼저 확인한다.

  • babel.config.js
  • index.js
  • src/pages/_404.tsx

그 다음 .ait 확인:

python3 - <<'PY'
import zipfile
import pathlib

ait_files = sorted(pathlib.Path('.').glob('*.ait'))
if not ait_files:
    raise SystemExit('No .ait file found')

path = str(ait_files[0])
print(path)

with zipfile.ZipFile(path) as z:
    for name in z.namelist():
        if name.startswith('bundle.') and name.endswith('.js'):
            print(name)
PY

실전 기준 요약

  • 새 프로젝트는 Apps in Toss 최소 설정으로 시작
  • buildait build
  • target: '0.84.0' 필수
  • .ait0_84_0 번들이 있는지 꼭 확인

템플릿 폴더

starter/template 폴더에는 새 프로젝트 시작용 기본 파일 세트를 넣어뒀다.

  • package.json
  • granite.config.ts
  • babel.config.js
  • index.js
  • tsconfig.json
  • require.context.ts
  • .watchmanconfig
  • src/_app.tsx
  • src/pages/index.tsx
  • src/pages/_404.tsx
  • check-apps-in-toss-build.sh

새 프로젝트를 만들 때는 이 폴더 내용을 복사한 다음 아래 값만 먼저 바꾸면 된다.

  • package.jsonname
  • granite.config.tsappName
  • granite.config.tsbrand.displayName
  • granite.config.tsbrand.icon
  • src/pages/index.tsx의 화면 문구