Wails의 작동 방식
Wails 아키텍처와 네이티브 성능을 구현하는 방식 이해하기
Wails는 백엔드에 Go를 사용하고 프런트엔드에 웹 기술을 사용하여 데스크톱 애플리케이션을 빌드하는 프레임워크입니다. 하지만 Electron과 달리 Wails는 브라우저를 번들로 포함하지 않고 운영 체제의 네이티브 WebView를 사용합니다.
Electron과의 주요 차이점:
| 항목 | Wails | Electron |
|---|---|---|
| 브라우저 | OS에서 제공하는 WebView | 번들로 포함된 Chromium(~100MB) |
| 백엔드 | Go(컴파일 방식) | Node.js(인터프리트 방식) |
| 통신 | 인메모리 브리지 | IPC(프로세스 간 통신) |
| 번들 크기 | ~15MB | ~150MB |
| 메모리 | ~10MB | ~100MB+ |
| 시작 시간 | <0.5초 | 2-3초 |
핵심 구성 요소
1. 네이티브 WebView
Wails는 운영 체제에 내장된 웹 렌더링 엔진을 사용합니다.
WebView2(Microsoft Edge WebView2)
- Chromium 기반(Edge 브라우저와 동일)
- Windows 10/11에 기본 설치됨
- Windows Update를 통한 자동 업데이트
- 최신 웹 표준 완벽 지원
WebKit(Safari의 렌더링 엔진)
- macOS에 내장됨
- Safari 브라우저와 동일한 엔진
- 뛰어난 성능과 배터리 사용 시간
- 최신 웹 표준 완벽 지원
WebKitGTK(WebKit의 GTK 포트)
- 패키지 관리자를 통해 설치
- GNOME Web(Epiphany)과 동일한 엔진
- 웹 표준을 충실히 지원
- 가볍고 성능이 우수함
이 점이 중요한 이유:
- 브라우저를 번들로 포함하지 않음 → 더 작은 앱 크기
- OS 네이티브 → 더 나은 통합과 성능
- 자동 업데이트 → OS 업데이트를 통해 보안 패치 적용
- 익숙한 렌더링 → 시스템 브라우저와 동일
2. Wails 브리지
브리지는 Wails의 핵심으로, Go와 JavaScript 간의 직접 통신을 지원합니다.
작동 방식:
- 프런트엔드가 Go 메서드를 호출(자동 생성된 바인딩을 통해)
- 브리지가 호출을 JSON으로 인코딩(메서드 이름 + 인수)
- 라우터가 등록된 서비스에서 Go 메서드를 찾음
- Go 메서드가 실행되고 값을 반환
- 브리지가 결과를 디코딩하여 프런트엔드로 다시 전송
- JavaScript에서 결과로 Promise가 이행됨
성능 특성:
- 인메모리: 네트워크 오버헤드와 HTTP가 없음
- 가능한 경우 제로 카피 적용(대용량 데이터)
- 기본적으로 비동기: 양쪽 모두 논블로킹 방식
- 타입 안전성: TypeScript 정의 자동 생성
3. 서비스 시스템
서비스는 Go 기능을 프런트엔드에 노출할 때 권장되는 방식입니다.
// Define a service (just a regular Go struct)
type GreetService struct {
prefix string
}
// Methods with exported names are automatically available
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
func (g *GreetService) GetTime() time.Time {
return time.Now()
}
// Register the service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{prefix: "Hello, "}),
},
})서비스 검색:
- Wails가 시작 시 구조체를 스캔합니다.
- 내보낸 메서드를 프런트엔드에서 호출할 수 있게 됩니다.
- TypeScript 바인딩을 위해 타입 정보를 추출합니다.
- 오류 처리가 자동으로 이루어집니다(Go 오류 → JS 예외).
생성된 TypeScript 바인딩:
// Auto-generated in frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function GetTime(): Promise<Date>서비스를 사용하는 이유
- 타입 안전성: TypeScript 완전 지원
- 자동 검색: 메서드를 수동으로 등록할 필요 없음
- 체계적인 구성: 관련 기능을 그룹화
- 테스트 가능: 서비스는 일반 Go 구조체일 뿐임
4. 이벤트 시스템
이벤트를 사용하면 컴포넌트 간에 게시/구독 통신을 수행할 수 있습니다.
사용 사례:
- 창 간 통신: 한 창에서 다른 창에 알림
- 백그라운드 작업: Go 서비스에서 UI에 진행 상황 알림
- 상태 동기화: 여러 창의 상태를 동기화
- 느슨한 결합: 컴포넌트 간 직접 참조가 필요 없음
예시:
// Go: Emit an event
app.Event.Emit("user-logged-in", user)// JavaScript: Listen for event
import { Events } from '@wailsio/runtime'
Events.On('user-logged-in', (user) => {
console.log('User logged in:', user)
})애플리케이션 수명 주기
수명 주기를 이해하면 리소스를 초기화하고 정리해야 하는 시점을 파악할 수 있습니다.
수명 주기 훅:
app := application.New(application.Options{
Name: "My App",
// Cleanly intercept quit requests (e.g. unsaved changes).
ShouldQuit: func() bool { return true },
// Called when the app is confirmed to be quitting — save state, close connections, etc.
OnShutdown: func() {},
})application.Options에는 OnStartup 필드가 없습니다. 시작 작업은 서비스의 ServiceStartup(ctx, options), app.Event.OnApplicationEvent(events.Common.ApplicationStarted, ...)을 통해 등록한 콜백 또는 단순히 app.Run() 이전에 배치해야 합니다.
빌드 프로세스
Wails가 애플리케이션을 빌드하는 방식을 알아봅니다.
빌드 단계:
- Go 코드 분석
- 서비스에서 내보낸 메서드 검색
- 매개변수 및 반환 타입 추출
- 메서드 시그니처 생성
- TypeScript 바인딩 생성
- 각 서비스의
.ts파일 생성 - 전체 타입 정의 포함
- JSDoc 주석 추가
- 프런트엔드 빌드
- 번들러 실행(Vite, webpack 등)
- 축소 및 최적화
frontend/dist/에 출력
- Go 컴파일
- 최적화 옵션을 적용하여 컴파일(
-ldflags="-s -w") - 빌드 메타데이터 포함
- 플랫폼별 컴파일
- 애셋 임베드
- 프런트엔드 파일을 Go 바이너리에 임베드
- 애셋 압축
- 단일 실행 파일 생성
결과: 모든 요소가 임베드된 하나의 네이티브 실행 파일입니다.
개발 환경과 프로덕션 환경
Wails는 개발 환경과 프로덕션 환경에서 다르게 동작합니다.
특징:
- 핫 리로드: 프런트엔드 변경 사항을 즉시 다시 로드
- 소스 맵: 원본 소스로 디버깅
- DevTools: 브라우저 DevTools 사용 가능
- 로깅: 상세 로깅 활성화
- 외부 프런트엔드: 개발 서버(Vite)에서 제공
작동 방식:
이점:
- 변경 사항에 대한 즉각적인 피드백
- 모든 디버깅 기능 사용 가능
- 더 빠른 반복 개발
특징:
- 임베드된 애셋: 프런트엔드를 바이너리에 포함하여 빌드
- 최적화: 축소 및 압축
- DevTools 없음: 기본적으로 비활성화
- 최소한의 로깅: 오류만 기록
- 단일 파일: 모든 요소를 하나의 실행 파일에 포함
작동 방식:
이점:
- 단일 파일 배포
- 더 작은 크기(최소화됨)
- 더 나은 성능
- 외부 종속성 없음
메모리 모델
메모리 사용 방식을 이해하면 효율적인 애플리케이션을 빌드할 수 있습니다.
메모리 영역:
- Go 힙
- 서비스와 애플리케이션 상태
- Go 가비지 컬렉터에서 관리
- 간단한 앱의 경우 일반적으로 5-10MB
- WebView 메모리
- DOM, JavaScript 힙, CSS
- WebView 엔진에서 관리
- 간단한 앱의 경우 일반적으로 10-20MB
- 브리지 메모리
- 통신용 메시지 버퍼
- 최소한의 오버헤드(<1MB)
- 가능한 경우 대용량 데이터를 복사 없이 처리
최적화 팁:
- 대용량 데이터 전송을 피하세요: ID를 전달하고 필요할 때 세부 정보를 가져오세요.
- 업데이트에는 이벤트를 사용하세요: 프런트엔드에서 폴링하지 마세요.
- 대용량 파일을 스트리밍하세요: 파일 전체를 메모리에 로드하지 마세요.
- 리스너를 정리하세요: 사용을 마치면 이벤트 리스너를 제거하세요.
보안 모델
Wails는 기본적으로 안전한 아키텍처를 제공합니다:
보안 기능:
- 메서드 허용 목록
- 내보낸 메서드만 호출 가능
- 비공개 메서드에는 접근 불가
- 서비스를 명시적으로 등록해야 함
- 타입 검증
- 인수를 Go 타입과 대조하여 검사
- 유효하지 않은 타입을 거부
- 인젝션 공격 방지
- eval() 없음
- 프런트엔드에서 임의의 Go 코드를 실행할 수 없음
- 미리 정의된 메서드만 호출 가능
- 동적 코드 실행 없음
- 컨텍스트 격리
- 각 창은 자체 컨텍스트를 사용
- 서비스에서 호출자 컨텍스트를 확인할 수 있음
- 창별 권한 설정 가능
권장 사항:
- Go에서 사용자 입력을 검증하세요(프런트엔드를 신뢰하지 마세요).
- 인증 및 권한 부여에 컨텍스트를 사용하세요.
- 파일 작업 전에 파일 경로를 정제하세요.
- 비용이 많이 드는 작업에 속도 제한을 적용하세요.
다음 단계
애플리케이션 수명 주기 - 시작, 종료 및 수명 주기 훅을 이해하세요. 자세히 알아보기 →
Go-프런트엔드 브리지 - 브리지의 작동 방식을 자세히 살펴보세요. 자세히 알아보기 →
빌드 시스템 - Wails가 애플리케이션을 빌드하는 방식을 이해하세요. 자세히 알아보기 →
빌드 시작하기 - 지금까지 배운 내용을 튜토리얼에서 적용해 보세요. 튜토리얼 →