Dart → JavaScript

AI
gemma-4-31b
작성자
익명
작성일
2026.08.17
조회수
13
버전
v1

Dart to JavaScript 컴파일 (Dart → JS)

1. 개요

Dart to JavaScript 컴파일은 Dart 언어로 작성된 소스 코드를 웹 브라우저가 해석하고 실행할 수 있는 JavaScript(JS) 코드로 변환하는 프로세스입니다. Dart는 강력한 타입 시스템과 객체 지향 기능을 제공하지만, 브라우저는 기본적으로 JavaScript만을 실행할 수 있으므로 웹 생태계에서 Dart를 활용하기 위해서는 컴파일러를 통한 변환 과정이 필수적입니다.

최근 Dart 생태계는 JS 컴파일을 넘어 Wasm(WebAssembly)으로의 컴파일을 적극적으로 지원하고 있습니다. 개발자는 웹 플랫폼의 성능 최적화 목적과 타겟 환경에 따라 JS 컴파일 경로와 Wasm 컴파일 경로를 선택적으로 사용할 수 있습니다.

2. 컴파일 방식 및 도구

Dart SDK는 사용 목적(개발 단계 vs 배포 단계)에 따라 서로 다른 두 가지 주요 컴파일러를 제공합니다.

2.1 dartdevc (Dart Development Compiler)

개발 단계에서 사용되는 증분 컴파일러(Incremental Compiler)입니다. 변경된 부분만 빠르게 컴파일하여 브라우저에 반영하는 '핫 리로드(Hot Reload)'와 유사한 경험을 제공하며, 디버깅 정보가 포함되어 있어 개발 효율성이 높습니다.

2.2 dart2js (Dart to JavaScript Compiler)

프로덕션 배포를 위한 최적화 컴파일러(AOT-like Compiler)입니다. 전체 소스 코드를 분석하여 사용되지 않는 코드를 제거하고, 실행 속도와 파일 크기를 최적화하는 데 집중합니다. 컴파일 시간이 길지만, 결과물의 성능은 매우 뛰어납니다.

[표] dart2js vs dartdevc 비교

구분 dartdevc dart2js
주 목적 빠른 개발 및 디버깅 최종 배포 및 성능 최적화
컴파일 방식 증분 컴파일 (Incremental) 전체 컴파일 (Full Compilation)
컴파일 속도 매우 빠름 느림
결과물 크기 큼 (디버그 정보 포함) 작음 (최적화 및 압축)
실행 성능 보통 높음

컴파일 실행 방법

사용 목적에 따라 다음과 같은 도구와 명령어를 통해 컴파일을 수행합니다.

  • 개발용: dartdevc는 단독 CLI보다는 webdev serve 명령어 또는 Flutter 툴체인(flutter run -d chrome)을 통해 내부적으로 호출되어 사용됩니다.
  • 배포용: dart compile js 명령어를 통해 dart2js 최적화 컴파일을 수행합니다.

# 배포용 최적화 컴파일 (dart2js 기반)
dart compile js -O4 bin/main.dart -o main.js

최적화 수준 옵션 (-O1 ~ -O4)

dart2js-O 옵션을 통해 최적화 강도를 조절할 수 있으며, 숫자가 높을수록 더 공격적인 최적화를 수행합니다.

  • -O1: 기본적인 최적화만 수행합니다. 컴파일 속도가 가장 빠르며, 디버깅이 비교적 용이합니다.
  • -O2: 일반적인 프로덕션 수준의 최적화를 수행합니다. 코드 크기와 실행 속도의 균형을 맞춥니다.
  • -O3: 더 강력한 최적화를 적용합니다. 불필요한 코드 제거(Tree Shaking)가 더 정밀하게 이루어지며 실행 성능이 향상됩니다.
  • -O4: 최대 최적화 단계입니다. 가장 작은 파일 크기와 최고의 실행 성능을 목표로 하며, 컴파일 시간이 가장 오래 걸립니다.

3. 변환 원리 및 매핑

Dart의 고수준 기능들은 JavaScript의 동적 특성에 맞게 다음과 같이 매핑됩니다.

3.1 타입 시스템

Dart는 정적 타입 언어이지만, JavaScript는 동적 타입 언어입니다. 컴파일 과정에서 Dart의 타입 체크는 컴파일 타임에 완료되며, 생성된 JS 코드에서는 대부분의 타입 정보가 제거(Type Erasure)됩니다. 다만, 런타임 타입 확인이 필요한 경우 JS의 instanceof나 내부적인 타입 태그를 사용하여 구현됩니다.

3.2 비동기 처리 매핑

Dart의 비동기 모델은 JavaScript의 이벤트 루프(Event Loop) 구조와 매우 유사하여 효율적으로 매핑됩니다. - <a href="/doc/%EA%B8%B0%EC%88%A0/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/%EB%B9%84%EB%8F%99%EA%B8%B0%20%EC%B2%98%EB%A6%AC/Future" class="wiki-link wiki-link-missing">Future</a> $\rightarrow$ <a href="/doc/%EA%B8%B0%EC%88%A0/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/%EB%B9%84%EB%8F%99%EA%B8%B0%20%EC%B2%98%EB%A6%AC/Promise" class="wiki-link wiki-link-missing">Promise</a>: Dart의 Future 객체는 JS의 Promise로 변환됩니다. await 키워드는 JS의 async/await 구문으로 매핑되어 비동기 흐름을 유지합니다. - Stream $\rightarrow$ Custom Implementation: JS에는 표준 Stream API가 Dart와 다르므로, Dart 컴파일러는 Stream의 동작을 모사하는 내부 런타임 라이브러리를 JS 코드로 함께 포함시켜 구현합니다.

4. JS 상호운용성 (JS Interop)

Dart 코드에서 JavaScript 라이브러리를 호출하거나, 반대로 JS에서 Dart 함수를 호출하는 메커니즘을 JS Interop이라고 합니다.

최신 Dart 버전에서는 정적 타입 안전성을 강화한 dart:js_interop 패키지 사용을 권장합니다.

import 'dart:js_interop';

// 외부 JS 함수 선언
@JS('window.alert')
external void jsAlert(String message);

// 외부 JS 객체 정의
@JS('localStorage')
external JSObject get localStorage;

void main() {
  jsAlert('Hello from Dart!'); // 브라우저 alert 창 호출
}

4.2 JS Interop 마이그레이션 가이드

기존의 package:js에서 최신 dart:js_interop로 전환할 때 주의할 점은 다음과 같습니다.

  1. 타입 변경 및 명시적 변환: JSObjectJSString과 같은 전용 타입을 사용해야 합니다. Dart 타입과 JS 타입 간의 변환을 위해 .toJS.toDart 확장 메서드를 사용합니다.
       // Dart String $\rightarrow$ JSString
       String dartString = "Hello";
       JSString jsString = dartString.toJS;
    
       // JSString $\rightarrow$ Dart String
       String backToDart = jsString.toDart;
       
  2. 어노테이션: @JS() 어노테이션의 사용 방식은 유사하나, 반환 타입과 매개변수 타입에 JS 접두사가 붙은 타입을 지정해야 합니다.
  3. 정적 분석: dart:js_interop은 컴파일 타임에 더 엄격한 타입 체크를 수행하므로, external 선언부의 시그니처를 JS 실제 API와 정확히 일치시켜야 합니다.

5. 브라우저 호환성 및 타겟 버전

Dart 컴파일러는 최신 브라우저 표준을 따르며, 호환성을 위해 다음과 같은 전략을 사용합니다.

  • 타겟 버전: 기본적으로 ES6(ECMAScript 2015) 이상의 문법을 사용합니다. dart2js가 생성하는 코드는 브라우저 호환성을 위해 상당히 보수적으로 최적화되어 생성됩니다.
  • 폴리필(Polyfill): 구형 브라우저(예: IE11 등) 지원이 필요한 경우, Dart 컴파일러 자체에서 지원하기보다는 컴파일된 JS 파일을 Babel과 같은 외부 트랜스파일러로 한 번 더 처리하거나, core-js와 같은 폴리필 라이브러리를 HTML에 포함시켜 부족한 API를 보완해야 합니다.
  • 호환성 전략: Flutter Web의 경우, canvaskit 렌더러를 사용하면 브라우저의 JS 엔진 의존도를 낮추고 WebAssembly(Wasm)를 통해 일관된 성능을 낼 수 있습니다.

6. 최적화 및 성능 고려사항

효율적인 JS 결과물을 얻기 위해 컴파일러는 다음과 같은 최적화 기법을 적용합니다.

6.1 트리 쉐이킹 (Tree Shaking)

사용되지 않는 코드(Dead Code)를 분석하여 최종 JS 파일에서 제거하는 과정입니다. Dart의 정적 분석 능력을 활용해 호출 그래프를 그리고, 진입점(Entry point)에서 도달할 수 없는 함수나 클래스를 삭제하여 파일 크기를 획기적으로 줄입니다.

6.2 코드 최소화 (Minification)

변수명을 짧게 변경하고 공백을 제거하여 파일 용량을 줄이는 과정입니다. dart2js는 이 과정을 내장하고 있어 별도의 도구 없이도 최적화된 번들을 생성합니다.

6.3 성능 병목 지점 및 해결책

  • 초기 로딩 속도: JS 파일이 커질수록 초기 파싱 시간이 증가합니다. 이를 해결하기 위해 지연 로딩(Deferred Loading)을 사용하여 필요한 시점에만 JS 모듈을 로드하도록 설정합니다.
      import 'package:my_app/heavy_module.dart' deferred as heavy;
      // ...
      await heavy.loadLibrary();
      

7. 활용 사례 및 생태계

가장 대표적인 사례는 Flutter Web입니다. Flutter는 단일 Dart 코드베이스로 iOS, Android 앱을 개발한 뒤, 동일한 로직을 dart2js 또는 dartdevc를 통해 웹 앱으로 배포합니다.

워크플로우 요약: 1. 개발 단계: flutter run -d chrome $\rightarrow$ dartdevc 작동 $\rightarrow$ 빠른 수정 및 확인. 2. 빌드 단계: flutter build web $\rightarrow$ dart2js 작동 $\rightarrow$ 트리 쉐이킹 및 최소화 적용. 3. 배포 단계: 생성된 main.dart.js 파일을 웹 서버(Nginx, S3 등)에 호스팅하여 사용자에게 제공.

AI 생성 콘텐츠 안내

이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.

주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.

이 AI 생성 콘텐츠가 도움이 되었나요?