Wails로 데스크톱 앱 만들기 #2 프로젝트 구조와 개발 루프 — wails dev와 바인딩
지난 글에서 wails init으로 프로젝트를 만들고 첫 창을 띄웠습니다. 이번 글은 그 프로젝트를 구성하는 파일들을 하나씩 열어 봅니다. 파일 몇 개 안 되지만 각각의 역할을 정확히 알아 두면, 이후 편에서 기능을 붙일 때 어디를 고쳐야 하는지 헤매지 않게 됩니다.
총 8편(본편 6편 + 심화 2편)입니다.
- #1 Wails란 — Go로 만드는 가벼운 데스크톱 앱
- #2 프로젝트 구조와 개발 루프 — wails dev와 바인딩 ← 이번 글
- #3 Go와 프론트엔드 연결 — 메서드 바인딩과 이벤트
- #4 시스템 통합 — 다이얼로그·메뉴·창 제어
- #5 실전 기능 — 설정 저장과 에러 처리
- #6 빌드와 배포 — 플랫폼별 패키징
wails init이 만든 것들 #
프로젝트 루트의 구조부터 확인합니다.
hello-wails/
├── main.go 앱의 진입점 — 창 옵션과 실행
├── app.go 프론트엔드에 노출할 Go 코드
├── wails.json 프로젝트 설정 (이름, 빌드 명령 등)
├── go.mod Go 모듈 정의
├── build/ 아이콘, 플랫폼별 빌드 리소스
└── frontend/ 웹 프론트엔드 전체
├── index.html
├── src/
├── dist/ 프론트엔드 빌드 산출물
└── wailsjs/ 자동 생성 바인딩 (직접 수정 금지)역할 분담이 명확합니다. Go 쪽 로직은 app.go에, 창과 실행 옵션은 main.go에, 화면은 frontend/에 둡니다. wails.json은 프로젝트 이름과 프론트엔드 빌드 명령 같은 설정을 담고, build/는 앱 아이콘과 설치 파일 리소스가 들어가는 곳으로 #6에서 다시 만납니다.
main.go 해부 #
main.go는 창을 정의하고 앱을 실행하는 진입점입니다.
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "hello-wails",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
OnStartup: app.startup,
Bind: []interface{}{
app,
},
})
if err != nil {
println("Error:", err.Error())
}
}주요 옵션은 네 가지입니다.
- Title, Width, Height — 창 제목과 초기 크기입니다.
- AssetServer — 프론트엔드 산출물을 앱에 내장해 서빙합니다.
go:embed지시문이frontend/dist를 바이너리 안에 넣기 때문에 단일 실행 파일 배포가 가능해집니다. - OnStartup — 앱 시작 시 호출될 함수입니다. 여기서 받은 context가 #3의 이벤트와 #4의 런타임 호출에 쓰입니다.
- Bind — 프론트엔드에 노출할 구조체 목록입니다. 바인딩의 입구입니다.
app.go — 바인딩의 출발점 #
app.go에는 프론트엔드에서 호출할 코드가 들어갑니다. 템플릿이 만들어 준 기본형은 이렇습니다.
type App struct {
ctx context.Context
}
func NewApp() *App {
return &App{}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name)
}Greet처럼 App에 붙은 공개 메서드는 Bind 설정을 통해 프론트엔드에서 호출 가능한 함수가 됩니다. 규칙과 세부 동작은 #3의 주제이므로, 여기서는 “Go 메서드를 추가하면 프론트엔드 함수가 하나 늘어난다"는 감각만 잡아 두면 됩니다.
템플릿 선택지 #
wails init -t로 지정하는 프론트엔드 템플릿은 취향대로 고를 수 있습니다.
| 템플릿 | 내용 |
|---|---|
| vanilla / vanilla-ts | 프레임워크 없는 순수 HTML/JS(TS) |
| react / react-ts | React + Vite |
| vue / vue-ts | Vue + Vite |
| svelte / svelte-ts | Svelte + Vite |
Wails 입장에서 프론트엔드는 정적 산출물을 내는 웹 프로젝트일 뿐이므로, 어느 템플릿을 골라도 Go 쪽 코드는 동일합니다. 이 시리즈는 다음 편부터 react-ts 템플릿을 기준으로 예제를 작성합니다. 리액트 기초 강좌를 마친 독자라면 프론트엔드 코드를 그대로 읽을 수 있습니다.
wails dev — 개발 루프 #
개발 중에는 wails dev 하나로 전체 루프가 돌아갑니다.
wails dev동작 방식이 파일 종류에 따라 다릅니다.
- 프론트엔드 수정 — Vite의 핫 리로드가 그대로 동작해, 저장하는 즉시 창에 반영됩니다.
- Go 코드 수정 — Wails가 변경을 감지해 백엔드를 다시 빌드하고 앱을 재시작합니다.
한 가지 유용한 사실이 있습니다. dev 모드에서는 같은 앱이 브라우저로도 열립니다.
To develop in the browser and call your bound Go methods,
navigate to: http://localhost:34115이 주소를 브라우저에서 열면 네이티브 창과 같은 앱이 뜨고, 바인딩된 Go 메서드도 그대로 호출됩니다. 브라우저 개발자 도구의 콘솔, 네트워크, 요소 검사를 전부 쓸 수 있으므로 프론트엔드 디버깅은 이쪽이 편합니다.
wailsjs — 자동 생성 바인딩 코드 #
템플릿 앱에서 인사말 기능이 동작하는 경로를 따라가 보면 바인딩의 정체가 보입니다. 프론트엔드 코드는 Go 메서드를 이렇게 가져다 씁니다.
import { Greet } from '../wailsjs/go/main/App';
Greet(name).then((result) => {
// result 에 Go 가 돌려준 문자열이 담김
});frontend/wailsjs/ 디렉터리는 wails dev가 Bind에 등록된 구조체를 읽어 자동 생성한 코드입니다. Go 메서드 하나마다 같은 이름의 자바스크립트 함수가 만들어지고, 호출 결과는 Promise로 돌아옵니다. 직접 수정할 파일이 아니며, Go 쪽 메서드를 바꾸면 다음 빌드에서 다시 생성됩니다.
정리하면 개발 루프는 이렇게 돌아갑니다. app.go에 메서드를 추가하면 wailsjs에 함수가 생기고, 프론트엔드에서 import 해 호출하면 결과가 Promise로 돌아옵니다. 이 흐름을 실제 기능으로 확장하는 것이 다음 글의 내용입니다.
마무리 #
이번 글의 핵심은 세 가지입니다.
- 프로젝트는 main.go(창·실행 옵션), app.go(노출할 로직), frontend/(화면), wails.json(설정)로 역할이 나뉩니다.
- wails dev는 프론트엔드 핫 리로드와 Go 재빌드를 함께 처리하고, 브라우저(localhost:34115)에서도 같은 앱을 디버깅할 수 있습니다.
- frontend/wailsjs는 Bind 목록에서 자동 생성되는 바인딩 코드로, Go 메서드를 Promise 기반 함수로 노출합니다.
다음 글인 “Wails로 데스크톱 앱 만들기 #3 Go와 프론트엔드 연결 — 메서드 바인딩과 이벤트"에서는 바인딩 규칙을 정확히 정리하고, 할 일 목록 백엔드를 직접 만들어 프론트엔드와 연결합니다. Go에서 프론트엔드로 데이터를 밀어 보내는 이벤트 시스템까지 다루겠습니다.