Wails로 데스크톱 앱 만들기 #2 프로젝트 구조와 개발 루프 — wails dev와 바인딩

5 분 소요

지난 글에서 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는 창을 정의하고 앱을 실행하는 진입점입니다.

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에는 프론트엔드에서 호출할 코드가 들어갑니다. 템플릿이 만들어 준 기본형은 이렇습니다.

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-tsReact + Vite
vue / vue-tsVue + Vite
svelte / svelte-tsSvelte + Vite

Wails 입장에서 프론트엔드는 정적 산출물을 내는 웹 프로젝트일 뿐이므로, 어느 템플릿을 골라도 Go 쪽 코드는 동일합니다. 이 시리즈는 다음 편부터 react-ts 템플릿을 기준으로 예제를 작성합니다. 리액트 기초 강좌를 마친 독자라면 프론트엔드 코드를 그대로 읽을 수 있습니다.

wails dev — 개발 루프 #

개발 중에는 wails dev 하나로 전체 루프가 돌아갑니다.

개발 서버 실행
wails dev

동작 방식이 파일 종류에 따라 다릅니다.

  • 프론트엔드 수정 — Vite의 핫 리로드가 그대로 동작해, 저장하는 즉시 창에 반영됩니다.
  • Go 코드 수정 — Wails가 변경을 감지해 백엔드를 다시 빌드하고 앱을 재시작합니다.

한 가지 유용한 사실이 있습니다. dev 모드에서는 같은 앱이 브라우저로도 열립니다.

wails dev 출력 일부
To develop in the browser and call your bound Go methods,
navigate to: http://localhost:34115

이 주소를 브라우저에서 열면 네이티브 창과 같은 앱이 뜨고, 바인딩된 Go 메서드도 그대로 호출됩니다. 브라우저 개발자 도구의 콘솔, 네트워크, 요소 검사를 전부 쓸 수 있으므로 프론트엔드 디버깅은 이쪽이 편합니다.

네이티브 창 자체에서도 우클릭 메뉴나 단축키로 개발자 도구(Inspect Element)를 열 수 있습니다. dev 모드에서 기본 활성화되어 있으므로, 창에서 바로 확인하고 싶을 때 활용하기 바랍니다.

wailsjs — 자동 생성 바인딩 코드 #

템플릿 앱에서 인사말 기능이 동작하는 경로를 따라가 보면 바인딩의 정체가 보입니다. 프론트엔드 코드는 Go 메서드를 이렇게 가져다 씁니다.

frontend/src/main.js — 호출하는 쪽
import { Greet } from '../wailsjs/go/main/App';

Greet(name).then((result) => {
    // result 에 Go 가 돌려준 문자열이 담김
});

frontend/wailsjs/ 디렉터리는 wails devBind에 등록된 구조체를 읽어 자동 생성한 코드입니다. 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에서 프론트엔드로 데이터를 밀어 보내는 이벤트 시스템까지 다루겠습니다.

X