Swing에서 JCEF로

Swing에서 JCEF로

1. 프로젝트 배경

Maro moti 플러그인은 IntelliJ 환경에서 Beat라는 화면 단위 컴포넌트를 자동 생성해주는 생산성 도구로 개발되었습니다.

해당 플러그인은 반복적으로 생성해야 하는 화면 구조와 필드 배치를 자동화하여 개발자의 생산성을 높이는 것을 목표로 하고 있었습니다.

사용자는 CreateBeatAction을 실행한 뒤 다이얼로그를 통해 화면 구조를 정의하고, 각 섹션에 필요한 필드를 배치한 후 최종적으로 코드 생성을 수행하게 됩니다.

초기 버전에서는 이 흐름을 Java Swing 기반 다이얼로그 두 개로 나누어 처리하고 있었습니다.

첫 번째 다이얼로그에서는 화면 섹션 구조를 정의하고, 두 번째 다이얼로그에서는 실제 필드 배치 및 매핑을 진행하는 구조였습니다.

처음에는 단순한 구조였기 때문에 큰 문제가 없어 보였지만, 실제 사용 빈도가 증가하면서 여러 UX 문제가 드러나기 시작했습니다.

특히 사용자는 두 개의 다이얼로그를 반복적으로 오가며 상태를 기억해야 했고, 이전 설정 내용을 다시 확인하기 위해 이전 단계로 돌아가는 일이 많았습니다.

또한 Swing 기반 와이어프레임 UI는 실제 화면과 차이가 커 사용자가 최종 결과를 직관적으로 예측하기 어려웠습니다.

프로젝트가 확장되면서 드래그 앤 드롭, 복잡한 레이아웃 구조, 실시간 미리보기 기능 등에 대한 요구사항도 증가했습니다.

하지만 Swing 기반 구조에서는 이러한 기능을 구현하기 위해 많은 이벤트 코드와 상태 관리 로직이 필요했으며, 유지보수 비용 또한 빠르게 증가하기 시작했습니다.

결국 단순한 UI 개선 수준이 아니라 아키텍처 자체를 재설계해야 한다는 판단에 도달했으며, 이를 위해 IntelliJ에서 제공하는 JCEF(Java Chromium Embedded Framework)를 활용한 웹 기반 구조 전환을 추진하게 되었습니다.

2. 기존 구조의 문제점

기존 구조에서 가장 큰 문제는 사용자 흐름이 여러 단계로 분리되어 있다는 점이었습니다.

[기존 화면]

image1.pngimage2.png

사용자는 먼저 섹션 구조를 설정한 뒤 다음 다이얼로그로 이동해 필드를 배치해야 했습니다.

이 과정에서 이전 설정 내용을 기억해야 했고, 수정이 필요하면 다시 이전 다이얼로그로 돌아가야 했습니다.

특히 복잡한 화면 구조를 다루는 경우 사용자는 섹션과 필드를 반복적으로 비교해야 했으며, 전체 화면 구조를 한 번에 파악하기 어려웠습니다.

이는 사용자의 피로도를 높였고 설정 과정 자체를 번거롭게 만들었습니다.

두 번째 문제는 Swing 기반 와이어프레임 UI의 한계였습니다.

기존 UI는 BoxLayout 기반으로 단순하게 구성되어 있었으며, 실제 서비스 UI와 시각적인 차이가 컸습니다.

예를 들어 실제 컴포넌트는 다양한 스타일과 정렬 구조를 가지고 있었지만, Swing 기반 와이어프레임에서는 단순한 박스 형태로만 표현되었습니다.

이로 인해 사용자는 최종 결과를 직관적으로 이해하기 어려웠고, 결과물을 생성한 뒤 다시 수정해야 하는 경우가 많았습니다.

세 번째 문제는 확장성이었습니다.

프로젝트가 커질수록 다음과 같은 기능 요구사항이 지속적으로 증가했습니다.

  • 필드 드래그 앤 드롭

  • 중첩 레이아웃 구조

  • 실시간 렌더링

  • 필드 순서 변경

  • 반응형 미리보기

  • 다양한 컴포넌트 지원

하지만 Swing 환경에서는 이러한 기능을 구현하기 위해 많은 이벤트 처리 코드와 상태 관리 코드가 필요했습니다.

특히 컴포넌트 간 상태 동기화가 복잡해질수록 유지보수 난이도가 급격히 상승했습니다.

UI 수정 과정에서도 문제가 발생했습니다.

레이아웃 구조가 복잡해질수록 작은 수정 하나에도 여러 이벤트 로직을 함께 수정해야 했고, 예상치 못한 사이드이펙트가 자주 발생했습니다.

결국 기능 확장 속도보다 유지보수 비용 증가 속도가 더 빨라지기 시작했으며, 새로운 UI 아키텍처 도입 필요성이 더욱 커졌습니다.

3. JCEF 기반 구조 전환

새로운 구조에서는 IntelliJ가 제공하는 JCEF(Java Chromium Embedded Framework)를 적극 활용했습니다.

JCEF는 IntelliJ 내부에 Chromium 브라우저를 임베딩할 수 있는 기능으로, 플러그인 내부에서도 사실상 웹 애플리케이션을 실행할 수 있도록 지원합니다.

이를 활용해 기존 Swing 기반 다이얼로그를 React 기반 웹 애플리케이션으로 전환했습니다.

새로운 구조에서는 JBCefBrowser를 통해 브라우저를 띄우고, 그 안에서 별도로 개발된 vui-editor React 애플리케이션을 실행하도록 구성했습니다.

이 구조의 가장 큰 장점은 UI 개발 방식을 완전히 웹 프론트엔드 방식으로 가져올 수 있다는 점이었습니다.

기존 Swing에서는 직접 구현해야 했던 다양한 인터랙션을 React와 프론트엔드 라이브러리를 활용해 훨씬 효율적으로 구현할 수 있게 되었습니다.

또한 컴포넌트 기반 개발 구조를 적용할 수 있게 되면서 재사용성과 유지보수성도 크게 향상되었습니다.

특히 상태 기반 렌더링 구조를 적용하면서 UI 상태 관리가 훨씬 직관적으로 변경되었습니다.

기존에는 이벤트 기반으로 여러 컴포넌트 상태를 수동으로 동기화해야 했지만, React 기반 구조에서는 상태 변경에 따라 화면이 자동으로 갱신되도록 구성할 수 있었습니다.

새로운 구조에서는 다음과 같은 목표를 달성하고자 했습니다.

  • 단일 다이얼로그 기반 UX 제공

  • 실시간 미리보기 지원

  • 풍부한 인터랙션 제공

  • 프론트엔드 개발 생산성 향상

  • 향후 기능 확장성 확보

  • 웹 기술 기반 유지보수 체계 구축

이러한 구조 전환은 단순한 UI 변경 작업이 아니라 IntelliJ 플러그인 UI 아키텍처 자체를 재설계하는 작업에 가까웠습니다.

4. Java와 JavaScript 간 통신

JCEF 기반 구조로 전환하면서 가장 먼저 해결해야 했던 문제는 Java와 JavaScript 간 데이터 교환이었습니다.

플러그인 내부에는 이미 Java 객체 형태로 관리되고 있는 ComponentElement, DataModel, 필드 목록 등의 데이터가 존재하고 있었습니다.

이 데이터를 React 애플리케이션의 초기 상태로 전달해야 했으며, 반대로 사용자가 편집한 결과를 다시 Java 측으로 전달하는 구조도 필요했습니다.

초기 데이터 전달은 evaluateJavaScript()를 활용해 구현했습니다.

다이얼로그 로드 완료 이후 window.initEditor() 함수를 호출해 JSON 형태의 데이터를 React 애플리케이션에 전달했습니다.

React 애플리케이션에서는 전달받은 데이터를 기반으로 초기 상태를 구성하고 화면을 렌더링하도록 설계했습니다.

반대로 저장 요청은 window.cefQuery()를 활용해 처리했습니다.

사용자가 저장 버튼을 누르면 React 애플리케이션이 현재 상태를 JSON 형태로 전달하고, Java 측 JBCefJSQuery 핸들러가 이를 수신해 내부 상태를 업데이트하도록 구성했습니다.

구현 과정에서는 여러 타이밍 이슈도 발생했습니다.

대표적으로 브라우저가 완전히 로드되기 전에 initEditor()가 호출되면 JavaScript 함수가 아직 준비되지 않아 초기화에 실패하는 문제가 있었습니다.

이를 해결하기 위해 DOM 로드 완료 이벤트 이후에만 초기 데이터를 주입하도록 변경했습니다.

또한 저장 콜백에서는 CountDownLatch를 활용해 일정 시간 동안 응답을 대기하도록 구성했으며, 최대 2초 이후에는 타임아웃 처리되도록 설계했습니다.

이 과정에서 예외 처리 및 브라우저 상태 검증 로직도 함께 추가해 안정성을 높였습니다.

5. 패키징과 CORS 문제 해결

JCEF 기반 구조에서 예상보다 어려웠던 부분 중 하나는 배포 환경이었습니다.

React 애플리케이션은 빌드 후 정적 리소스 형태로 생성되며, 최종적으로 IntelliJ 플러그인 JAR 내부에 포함되어 배포됩니다.

문제는 JCEF에서 file:// 프로토콜 기반으로 React 앱을 로드할 경우 브라우저의 CORS 정책으로 인해 일부 기능이 정상 동작하지 않는다는 점이었습니다.

특히 fetch API 호출이나 동적 리소스 로딩 과정에서 차단 문제가 발생했습니다.

초기에는 단순히 로컬 파일을 직접 로드하는 방식으로 접근했지만, 실제 운영 환경에서는 다양한 제약이 발생했습니다.

이를 해결하기 위해 JAR 내부 리소스를 임시 디렉터리에 추출한 뒤, 간단한 로컬 HTTP 서버를 실행해 localhost 기반으로 리소스를 서빙하도록 구조를 변경했습니다.

HTTP 서버 구현에는 com.sun.net.httpserver.HttpServer를 활용했습니다.

브라우저 입장에서는 일반적인 HTTP 요청 환경으로 인식하게 되었고, 결과적으로 CORS 문제를 해결할 수 있었습니다.

또한 개발 환경에서는 Vite 개발 서버(localhost:5173)와 직접 연결해 개발할 수 있도록 구성했습니다.

이를 통해 핫리로드 환경을 그대로 활용할 수 있었고, 프론트엔드 개발 생산성이 크게 향상되었습니다.

특히 UI 수정 시 IntelliJ 플러그인을 다시 빌드하지 않고도 바로 변경 사항을 확인할 수 있었기 때문에 반복 개발 속도가 매우 빨라졌습니다.

결과적으로 개발 환경과 운영 환경 모두 안정적으로 구성할 수 있었으며, 웹 기반 UI 개발 경험을 IntelliJ 플러그인 내부에서도 자연스럽게 활용할 수 있게 되었습니다.

6. 결과 및 개선 효과

JCEF 기반 구조로 전환한 이후 가장 크게 개선된 부분은 사용자 경험이었습니다.

[새로운 화면]

image3.png

기존에는 두 단계로 분리되어 있던 흐름을 하나의 다이얼로그 안으로 통합하면서 사용자는 전체 화면 구조와 필드 구성을 동시에 확인할 수 있게 되었습니다.

이제 사용자는 다음 작업을 한 화면 안에서 모두 처리할 수 있게 되었습니다.

  • 섹션 구조 생성

  • 필드 배치

  • 순서 변경

  • 드래그 앤 드롭

  • 실시간 미리보기

  • 레이아웃 확인

특히 실시간 렌더링 기능 덕분에 사용자는 현재 편집 중인 화면 결과를 즉시 확인할 수 있었고, 최종 결과 예측도 훨씬 쉬워졌습니다.

UI/UX 측면에서도 큰 개선이 있었습니다.

React 기반 구조로 전환하면서 다양한 프론트엔드 라이브러리와 상태 관리 패턴을 활용할 수 있게 되었고, 애니메이션 및 인터랙션 구현도 훨씬 자연스러워졌습니다.

개발 조직 측면에서도 긍정적인 변화가 있었습니다.

기존에는 UI 수정도 모두 IntelliJ 플러그인 코드 내부에서 처리해야 했지만, 이제는 프론트엔드와 백엔드 작업을 독립적으로 분리할 수 있게 되었습니다.

프론트엔드 개발자는 React 애플리케이션에 집중해서 작업할 수 있었고, 플러그인 개발자는 Java 기반 비즈니스 로직에 집중할 수 있었습니다.

덕분에 두 개발자가 병렬로 작업할 수 있게 되었으며, 개발 속도와 유지보수 효율도 크게 향상되었습니다.

이번 프로젝트를 통해 IntelliJ 플러그인 개발에서도 웹 기술을 적극적으로 활용할 수 있다는 가능성을 확인할 수 있었습니다.

특히 복잡한 인터랙션과 풍부한 사용자 경험이 필요한 도구라면 JCEF 기반 구조가 매우 효과적인 선택지가 될 수 있다는 점을 체감할 수 있었습니다.

DEVKC

Site footer