Flutter 튜토리얼 60편: WebAssembly와 웹 고급

개요#

Flutter 웹 앱의 성능을 극대화하고 고급 기능을 활용하는 방법을 알아봅니다. WebAssembly1를 통한 네이티브 수준의 성능, 앱 초기화 커스터마이징, 기존 웹 앱에 Flutter 임베딩, 그리고 웹 이미지 처리까지 살펴보겠습니다.

graph TB subgraph "Flutter 웹 고급 기능" A[WebAssembly 컴파일] --> B[네이티브 성능] C[앱 초기화 커스터마이징] --> D[로딩 최적화] E[Flutter 임베딩] --> F[하이브리드 앱] G[웹 이미지 처리] --> H[CORS 해결] end

1. WebAssembly (Wasm) 지원#

Why: 왜 WebAssembly를 사용하나요?#

WebAssembly는 JavaScript보다 훨씬 빠른 실행 속도를 제공합니다. 복잡한 애니메이션, 대량의 데이터 처리, 게임 등 성능이 중요한 웹 앱에서 큰 이점을 얻을 수 있습니다.

What: WebAssembly란 무엇인가요?#

Flutter는 Dart 코드를 WebAssembly로 컴파일하여 브라우저에서 실행할 수 있습니다. 이를 통해 네이티브에 가까운 성능을 웹에서도 경험할 수 있습니다.

특징JavaScriptWebAssembly
실행 속도인터프리터/JIT네이티브에 가까움
파일 크기텍스트 기반바이너리 (압축)
시작 시간파싱 필요즉시 실행
메모리가비지 컬렉션수동 관리 가능

How: WebAssembly 빌드 방법#

1단계: Flutter 버전 확인#

Terminal window
# Flutter 3.24 이상 필요
flutter upgrade
flutter --version

2단계: 의존성 호환성 확인#

WebAssembly 컴파일을 위해서는 새로운 JS interop2 라이브러리를 사용해야 합니다.

# pubspec.yaml - 호환되는 라이브러리 사용
dependencies:
web: ^0.5.0 # dart:html 대신 사용
// 기존 (Wasm 미지원)
import 'dart:html';
// 새로운 방식 (Wasm 지원)
import 'package:web/web.dart';
import 'dart:js_interop';

3단계: index.html 업데이트#

Terminal window
# web 디렉토리 재생성 (최신 초기화 방식 적용)
flutter create . --platforms web

4단계: Wasm으로 실행/빌드#

Terminal window
# 개발 모드 실행
flutter run -d chrome --wasm
# 프로덕션 빌드
flutter build web --wasm

Wasm 실행 여부 확인#

class WasmChecker extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 빌드 시점에 결정되는 환경 변수 (권장)
const isWasm = bool.fromEnvironment('dart.tool.dart2wasm');
// 또는 런타임에 확인 (대안)
// final isWasm = identical(double.nan, double.nan);
return Center(
child: Text(
isWasm ? 'WebAssembly로 실행 중' : 'JavaScript로 실행 중',
style: TextStyle(fontSize: 24),
),
);
}
}

Watch out: 주의사항#

멀티스레딩을 위한 HTTP 헤더 설정#

Wasm 멀티스레딩을 사용하려면 서버에서 특정 HTTP 헤더를 설정해야 합니다.

헤더
Cross-Origin-Embedder-Policycredentialless 또는 require-corp
Cross-Origin-Opener-Policysame-origin

브라우저 호환성#

graph LR A[WasmGC 지원] --> B[Chrome 119+] A --> C[Edge 119+] D[현재 미지원] --> E[Firefox] D --> F[Safari] D --> G[iOS 모든 브라우저]

2. 웹 앱 초기화 커스터마이징#

Why: 왜 초기화를 커스터마이징하나요?#

기본 초기화 대신 커스텀 로직을 추가하여 로딩 화면 표시, 설정 변경, 서비스 워커 제어 등을 할 수 있습니다.

What: 초기화 토큰이란?#

Flutter 빌드 시 특별한 토큰들이 실제 코드로 대체됩니다.

토큰설명
{{flutter_js}}FlutterLoader를 사용 가능하게 하는 JS 코드
{{flutter_build_config}}빌드 메타데이터 설정
{{flutter_service_worker_version}}서비스 워커 버전 번호
{{flutter_bootstrap_js}}부트스트랩 스크립트 전체 내용

How: 커스텀 초기화 구현#

기본 부트스트랩 스크립트#

web/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Flutter App</title>
</head>
<body>
<script>
{{flutter_bootstrap_js}}
</script>
</body>
</html>

커스텀 부트스트랩 스크립트#

web/flutter_bootstrap.js
{{flutter_js}}
{{flutter_build_config}}
// 로딩 표시
const loading = document.createElement('div');
loading.id = 'loading';
loading.innerHTML = '<h1>로딩 중...</h1>';
document.body.appendChild(loading);
_flutter.loader.load({
onEntrypointLoaded: async function(engineInitializer) {
loading.innerHTML = '<h1>엔진 초기화 중...</h1>';
const appRunner = await engineInitializer.initializeEngine();
loading.innerHTML = '<h1>앱 시작 중...</h1>';
await appRunner.runApp();
// 로딩 요소 제거
loading.remove();
}
});

설정 옵션#

_flutter.loader.load({
config: {
// 렌더러 지정 (canvaskit 또는 skwasm)
renderer: 'canvaskit',
// CanvasKit 설정
canvasKitBaseUrl: '/canvaskit/',
canvasKitVariant: 'auto', // auto, full, chromium
canvasKitForceCpuOnly: false,
canvasKitMaximumSurfaces: 8,
// Wasm 싱글스레드 강제 (호환성용)
forceSingleThreadedSkwasm: true,
// 에셋 경로 설정 (CDN 사용 시)
assetBase: 'https://cdn.example.com/assets/',
// 디버깅
debugShowSemanticNodes: false,
}
});

URL 파라미터로 렌더러 선택#

web/flutter_bootstrap.js
{{flutter_js}}
{{flutter_build_config}}
const searchParams = new URLSearchParams(window.location.search);
const renderer = searchParams.get('renderer');
const userConfig = renderer ? { 'renderer': renderer } : {};
_flutter.loader.load({
config: userConfig,
});
// 사용: https://myapp.com/?renderer=skwasm

3. 기존 웹 앱에 Flutter 임베딩#

Why: 왜 Flutter를 임베딩하나요?#

기존 웹 사이트의 일부 섹션만 Flutter로 구현하거나, 점진적으로 Flutter로 마이그레이션할 때 유용합니다.

What: 임베딩 모드의 종류#

graph TB subgraph "임베딩 방식" A[전체 페이지 모드] --> B[Flutter가 전체 화면 제어] C[iframe 임베딩] --> D[격리된 Flutter 앱] E[임베디드 모드] --> F[특정 요소에 렌더링] E --> G[멀티뷰 지원] end
모드설명사용 사례
전체 페이지기본 모드, 전체 화면 사용독립 웹 앱
iframe별도 문서로 격리위젯 형태 삽입
임베디드HTML 요소에 직접 렌더링하이브리드 앱

How: 임베딩 구현 방법#

iframe 임베딩 (가장 간단)#

<!-- 호스트 페이지 -->
<iframe
src="https://my-flutter-app.com/index.html"
width="800"
height="600"
style="border: none;">
</iframe>

멀티뷰 임베디드 모드#

JavaScript 설정:

flutter_bootstrap.js
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
onEntrypointLoaded: async function(engineInitializer) {
let engine = await engineInitializer.initializeEngine({
multiViewEnabled: true, // 임베디드 모드 활성화
});
let app = await engine.runApp();
// 전역으로 노출하여 호스트 앱에서 사용
window.flutterApp = app;
}
});

호스트 페이지에서 뷰 관리:

// 뷰 추가
let viewId = window.flutterApp.addView({
hostElement: document.querySelector('#flutter-container'),
initialData: {
greeting: '안녕하세요!',
userId: 12345,
}
});
// 뷰 제거
window.flutterApp.removeView(viewId);

Dart에서 멀티뷰 처리:

lib/main.dart
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
void main() {
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyApp(),
),
);
}
/// 각 뷰에 대해 위젯을 생성하는 래퍼
class MultiViewApp extends StatefulWidget {
const MultiViewApp({super.key, required this.viewBuilder});
final WidgetBuilder viewBuilder;
@override
State<MultiViewApp> createState() => _MultiViewAppState();
}
class _MultiViewAppState extends State<MultiViewApp>
with WidgetsBindingObserver {
Map<Object, Widget> _views = {};
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_updateViews();
}
@override
void didChangeMetrics() {
_updateViews();
}
void _updateViews() {
final newViews = <Object, Widget>{};
for (final view in WidgetsBinding.instance.platformDispatcher.views) {
newViews[view.viewId] = _views[view.viewId] ??
View(
view: view,
child: Builder(builder: widget.viewBuilder),
);
}
setState(() => _views = newViews);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewCollection(views: _views.values.toList());
}
}

뷰 식별 및 초기 데이터#

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 현재 뷰 ID 가져오기
final viewId = View.of(context).viewId;
// 초기 데이터 가져오기 (JS interop 필요)
// final initialData = ui_web.views.getInitialData(viewId);
return Center(
child: Text('뷰 ID: $viewId'),
);
}
}

hostElement로 단일 뷰 임베딩#

// 멀티뷰 없이 특정 요소에 렌더링
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});
<div id="flutter_host" style="width: 100%; height: 400px;"></div>

4. 웹 이미지 표시#

Why: 왜 웹 이미지 처리가 특별한가요?#

웹 브라우저의 보안 정책(CORS3)으로 인해 다른 도메인의 이미지를 직접 처리하는 데 제한이 있습니다.

What: 이미지 표시 방식 비교#

방식장점단점
<img> 태그캐싱, 최적화 자동픽셀 접근 불가
Canvas drawImage픽셀 조작 가능CORS 제한
WebGL최고 성능, 셰이더복잡한 구현

CORS 문제 이해하기#

sequenceDiagram participant App as Flutter 앱 participant Browser as 브라우저 participant Server as 이미지 서버 App->>Browser: 이미지 요청 Browser->>Server: HTTP 요청 Server->>Browser: 응답 (CORS 헤더 확인) alt CORS 허용 Browser->>App: 이미지 데이터 전달 else CORS 거부 Browser->>App: 차단됨 end

How: CORS 문제 해결 방법#

1. 같은 출처 이미지 사용#

// 에셋 이미지
Image.asset('assets/images/photo.png')
// 같은 도메인 네트워크 이미지
Image.network('/images/photo.png')
// 메모리 이미지
Image.memory(bytes)

2. CORS 활성화된 CDN 사용#

Firebase Hosting 예시:

firebase.json
{
"hosting": {
"headers": [
{
"source": "**/*.@(jpg|jpeg|png|gif|webp)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
]
}
}

3. CORS 프록시 사용#

class ImageProxy {
// CloudFlare Workers나 자체 서버를 통한 프록시
static String getProxiedUrl(String originalUrl) {
return 'https://my-cors-proxy.com/?url=${Uri.encodeComponent(originalUrl)}';
}
}
// 사용
Image.network(ImageProxy.getProxiedUrl('https://external-site.com/image.jpg'))

4. HTML 플랫폼 뷰 사용#

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class WebImage extends StatelessWidget {
final String url;
const WebImage({super.key, required this.url});
@override
Widget build(BuildContext context) {
if (kIsWeb) {
// 웹에서는 HTML img 태그 사용
return HtmlElementView(
viewType: 'img-view-$url',
onPlatformViewCreated: (id) {
// 플랫폼 뷰 생성 시 처리
},
);
}
// 다른 플랫폼에서는 일반 Image 사용
return Image.network(url);
}
}

Watch out: 주의사항#

  • 보안: 프록시 서버를 사용할 때 신뢰할 수 있는 이미지만 프록시하세요.
  • 성능: HTML 플랫폼 뷰는 Flutter 위젯보다 성능이 낮을 수 있습니다.
  • 캐싱: 네트워크 이미지는 cached_network_image 패키지 사용을 고려하세요.

5. 종합 예제: 고급 웹 앱 설정#

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const AdvancedWebApp());
}
class AdvancedWebApp extends StatelessWidget {
const AdvancedWebApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Advanced Web App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter 웹 고급 기능'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildInfoCard(
'실행 환경',
_getExecutionEnvironment(),
Icons.computer,
),
const SizedBox(height: 16),
_buildInfoCard(
'렌더러',
kIsWeb ? '웹 렌더러 사용 중' : '네이티브 렌더러',
Icons.brush,
),
const SizedBox(height: 16),
_buildInfoCard(
'WebAssembly',
_isWasm() ? '활성화됨' : '비활성화됨',
Icons.speed,
),
],
),
);
}
String _getExecutionEnvironment() {
if (kIsWeb) {
return '웹 브라우저';
} else if (defaultTargetPlatform == TargetPlatform.android) {
return 'Android';
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return 'iOS';
} else {
return '데스크톱';
}
}
bool _isWasm() {
return const bool.fromEnvironment('dart.tool.dart2wasm');
}
Widget _buildInfoCard(String title, String value, IconData icon) {
return Card(
child: ListTile(
leading: Icon(icon, size: 40),
title: Text(title),
subtitle: Text(value),
),
);
}
}

마무리#

이번 튜토리얼에서는 Flutter 웹의 고급 기능들을 살펴보았습니다.

핵심 요약#

기능핵심 포인트
WebAssembly--wasm 플래그로 빌드, 새로운 JS interop 필요
앱 초기화flutter_bootstrap.js 커스터마이징
Flutter 임베딩multiViewEnabled로 멀티뷰 지원
웹 이미지CORS 정책 이해 및 우회 방법

다음 단계#

  • 61편에서는 데스크톱 앱 개발을 다룹니다.
  • 실제 프로젝트에 WebAssembly를 적용해보세요.
  • 기존 웹사이트에 Flutter 위젯을 임베딩해보세요.

참고 자료#

Footnotes#

  1. WebAssembly (Wasm): 웹 브라우저에서 실행되는 이진 명령어 형식으로, JavaScript보다 훨씬 빠른 실행 속도를 제공합니다.

  2. JS interop: Dart 코드에서 JavaScript 코드와 상호작용하기 위한 인터페이스입니다.

  3. CORS (Cross-Origin Resource Sharing): 웹 브라우저가 다른 출처의 리소스에 대한 접근을 제어하는 보안 메커니즘입니다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 60편: WebAssembly와 웹 고급
https://moodturnpost.net/posts/flutter/flutter-platform-web-advanced/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차