TypeScript를 활용한 Electron 데스크톱 애플리케이션 구축 방법

프로젝트 구조 설계

TypeScript와 Electron을 함께 사용한다면 컴파일 산출물과 원천 소스를 명확히 격리하는 디렉토리 레이아웃이 필수적입니다. 다음과 같은 계층적 구조를 기반으로 빈 파일을 생성합니다.

my-electron-app/
├── dist/                // tsc 컴파일된 번들 및 JS 파일 위치
├── src/                 // TypeScript 원천 코드 루트
│   ├── main.ts          // 메인 프로세스 진입점
│   ├── preload.ts       // 보안 브릿지 역할 스크립트
│   └── renderer.ts      // UI 렌더링 및 이벤트 처리
├── index.html           // 프레젠테이션 레이어 템플릿
├── package.json         // 패키지 메타데이터 및 실행 스크립트
└── tsconfig.json        // 타입스크립트 컴파일 옵션 정의

설정 파일 구성

1. package.json 관리

애플리케이션의 진입점을 컴파일 출력 경로로 지정하고, 일괄 빌드 및 런칭 명령어를 정의합니다.

{
  "name": "ts-electron-starter",
  "version": "1.0.0",
  "description": "TypeScript + Electron 통합 샘플",
  "main": "./dist/main.js",
  "scripts": {
    "build": "tsc",
    "start": "npm run build && electron ./dist/main.js"
  },
  "devDependencies": {}
}

2. 의존성 설치

네트워크 가속용 미러 변수를 설정한 후, 필요한 패키지를 개발 환경 의존성으로 추가합니다.

# Windows PowerShell 또는 CMD 환경 변수 설정
$env:ELECTRON_MIRROR = "https://npmmirror.com/mirrors/electron/"
# npm 또는 yarn을 통한 설치
npm install --save-dev electron typescript @types/node

3. tsconfig.json 최적화

CommonJS 모듈 체계와 ES6 대상 타겟팅을 명시하며, 엄격한 타입 검사를 활성화합니다.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

4. VS Code 디버깅 환경 (launch.json)

메인 프로세스와 렌더러 프로세스를 동시에 추종할 수 있도록 복합 구성 세트를 설정합니다.

{
  "version": "0.2.0",
  "compounds": [
    {
      "name": "Full App Debugger",
      "configurations": ["Main Process", "Renderer Debug"],
      "stopOnEntry": false
    }
  ],
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Main Process",
      "program": "${workspaceFolder}/node_modules/.bin/electron.cmd",
      "args": [".", "--remote-debugging-port=9229"],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal"
    },
    {
      "type": "chrome",
      "request": "attach",
      "name": "Renderer Debug",
      "port": 9229,
      "webRoot": "${workspaceFolder}"
    }
  ]
}

디버거 실행 패널에서 Full App Debugger 프로필을 선택하여 시작합니다.

핵심 로직 구현

1. main.ts (메인 프로세스)

애플리케이션 생명주기를 제어하고 브라우저 창을 관리합니다. IPC 채널을 통해 렌더러에게 데이터를 동기식으로 제공합니다.

import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';

let activeWindow: BrowserWindow | null = null;

function createMainWindow(): void {
  activeWindow = new BrowserWindow({
    width: 1024,
    height: 768,
    webPreferences: {
      preload: path.join(__dirname, 'preload-bundle.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });

  activeWindow.loadFile(path.join(__dirname, '..', 'index.html'));
  
  activeWindow.on('closed', () => { activeWindow = null; });
}

app.whenReady().then(createMainWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createMainWindow();
});

// Renderer 요청 처리 인터셉터
ipcMain.handle('query-system-metadata', async () => {
  return {
    appTitle: app.getName(),
    versionNumber: app.getVersion(),
    platformInfo: process.platform
  };
});

2. preload.ts (보안 브리지)

contextIsolation이 활성화된 현대적 Electron 환경에서 렌더러 프로세스에게 안전한 API 레이어를 노출합니다.

import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('DesktopBridge', {
  fetchSystemData: (): Promise<{ appTitle: string; versionNumber: string; platformInfo: string }> => 
    ipcRenderer.invoke('query-system-metadata')
});

3. renderer.ts (UI 프로세스)

DOM 조작을 통해 비동기적으로 수신된 데이터를 화면에 렌더링합니다. 확장된 전역 타입을 사용하여 정적 검증을 수행합니다.

// 전역 확장형 타입 정의
interface WindowExtension extends Window {
  DesktopBridge: {
    fetchSystemData(): Promise<{ appTitle: string; versionNumber: string; platformInfo: string }>;
  };
}

// 초기화 흐름
async function initUI(): Promise<void> {
  try {
    const data = await (window as WindowExtension).DesktopBridge.fetchSystemData();
    const targetNode = document.getElementById('data-output');
    
    if (targetNode) {
      targetNode.innerHTML = `
        <p><strong>애플리케이션:</strong> ${data.appTitle}</p>
        <p><strong>빌드 버전:</strong> ${data.versionNumber}</p>
        <p><strong>실행 환경:</strong> ${data.platformInfo}</p>
      `;
    }
  } catch (err) {
    console.error('브릿지 데이터 전송 실패:', err);
  }
}

document.addEventListener('DOMContentLoaded', initUI);

4. index.html (렌더링 템플릿)

CSP(Content Security Policy) 전략을 적용하여 외부 코드 주입을 차단하고 로컬 번들을 연결합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" />
  <title>TS+Electron Demo</title>
</head>
<body>
  <header><h1>데스크톱 환경 테스트</h1></header>
  <main id="data-output">시스템 정보 로딩 중...</main>
  <script src="./dist/renderer.js"></script>
</body>
</html>

컴파일 및 실행

모든 원천 코드가 완성되었으면 다음 단계로 진행하여 실행 가능한 상태까지 전환합니다.

# 타입스크립트 문법 검사 및 JavaScript 변환
npm run build

# 변환된 결과를 기반으로 Electron 애플리케이션 실행
npm start

명령어 실행이 완료되면 지정된 해상도의 윈도우가 생성되며, DOM에 바인딩된 시스템 메타데이터가 즉시 반영됩니다.

태그: Electron TypeScript Node.js IPC 통신 데스크톱 환경 구성

9월 24일 07:21에 게시됨