블로그
만들었습니다.
사실 개발 블로그를 운영할 계획은 있었으나 실천의 문제 였는데 Flutter 3.7의 웹 임베딩, 최근 회사에 맡게된 웹 프로젝트를 위해 웹 공부를 하다보니 차라리 복습도 할겸 만드는게 낫다는 생각이 들었습니다.
주로 인터랙션 디자인, 간단한 토이 프로젝트 올릴 예정입니다.
Flutter앱 임베딩
블로그를 직접 만들게된 가장 큰 이유입니다. 이전의 Flutter 웹도 iframe
을 이용해 렌더링이 가능했지만 3.7 버전으로 올라가면서 google maps
나 trading view
처럼 임베딩이 가능해져서
더 최적화된 Flutter 위젯을 웹에서 사용할 수 있게 됐습니다.
Flutter앱은 어떻게 렌더링되나
아래와 같은 과정으로 우리의 웹 페이지에 렌더링 됩니다.
- flutter.js 파일은 Flutter 위젯을 불러오는 FlutterLoader파일을 정의하고 있습니다.
- FlutterLoader는 js파일로 변환된 dart파일을 불러와 실행합니다.
- 실행의 결과물을 원하는 엘리먼트에 렌더링을 합니다. 엘리먼트를 지정하지 않으면 body에 렌더링을 하게됩니다.
Let's try out
아래는 플러터 위젯이 임베디드 된 리액트 컴포넌트 예시입니다.
클릭하면 파도타기하는 애니메이션은 Flutter단에서, 컴포넌트가 점프하는 애니메이션은 리액트 단에서 줬습니다. 이번 글에서는 Flutter, Next.js 각각에서 중요하다고 생각했던 부분만 소개를하고 전체 코드는 글 아래에 github링크를 달아두겠습니다.
Flutter
void main() {
runApp(const HelloNext());
}
class HelloNext extends StatefulWidget {
const HelloNext({super.key});
@override
State<HelloNext> createState() => _HelloNextState();
}
class _HelloNextState extends State<HelloNext> with SingleTickerProviderStateMixin {
...
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(
...
HelloNext위젯을 MaterialApp
으로 감싸지 않은걸 알수가 있습니다.
웹 페이지에서 Flutter 위젯을 렌더링하는 것은 Flutter 앱이 실행된다는 것을 의미합니다. 그러나 이로인해 url뒤에 #/
이 붙게 되고 라우팅이 꼬이는 문제가 발생합니다. 이건 Flutter 프레임워크의 해시 라우팅이 적용되었다는 의미입니다.
우리는 복잡한 앱이 아닌 단일 위젯만 필요하므로 MaterialApp
의 라우팅 시스템은 필요하지 않습니다. 그러나 MaterialApp
은 라우팅 시스템 뿐만 아니라 현지화도 처리합니다. 따라서 생략하게 되면 TextDirection
을 결정할 수 없기때문에 Directionality
위젯으로 감싸야 합니다.
Next.js
declare let _flutter: any;
function FlutterWidget(props: {dirName: string, height: number}) {
...
useEffect(() => {
const flutterBundle = document.createElement('script');
flutterBundle.src = `/flutter/${dirName}/flutter.js`;
flutterBundle.defer = true;
document.head.appendChild(flutterBundle);
flutterBundle.addEventListener('load', () => {
_flutter.loader.loadEntrypoint({
entrypointUrl: `/flutter/${dirName}/main.dart.js`,
onEntrypointLoaded: async function (engineInitializer: any) {
let appRunner = await engineInitializer.initializeEngine({
assetBase: `/flutter/${dirName}/`,
canvasKitBaseUrl: `/flutter/${dirName}//canvaskit/`,
hostElement: document.querySelector('#flutter_target'),
renderer: 'canvaskit'
})
await appRunner.runApp();
}
});
});
return () => {
document.head.removeChild(flutterBundle);
}
}, [ dirName ]);
return (
<div id='flutter_target' />
)
}
export default FlutterWidget;
- 정적 블로그이므로 Flutter 웹 컴파일 결과물을
public
디렉토리에 저장하고 가져오는 방식을 사용합니다. 모든 Flutter 위젯에 대해 리액트 컴포넌트를 만드는 것은 비효율적이라고 판단하여, 각 위젯에 대해 별도로 정의하였습니다. 이 정의된 위젯은 props로 대상의 경로를 받습니다. useEffect
블록에서 script 엘리먼트를 생성하여 대상 경로의flutter.js
를 로드합니다. 이후_flutter
로 정의된FlutterLoader
객체를 사용하여 Dart 파일을 실행합니다.head
안에서flutter.js
를 확인할 수 있으며,body
안에서main.dart.js
를 확인할 수 있습니다.- Flutter엔진의 초기화 코드에서 설정한 hostElement의 id와 같은 엘리먼트를 리턴합니다.
++ declare
키워드를 이용해 _flutter 변수를 미리 선언해줍니다. 그렇지 않으면 컴파일 타임에 _flutter 변수를 찾지못해 빨간 밑줄이 그어집니다. 실행에는 문제가 없지만 시각적으로 거슬리기 때문에 미리 선언해줬습니다.
요약
- Flutter web 프로젝트 빌드 결과물을 Next.js프로젝트로 복사 한다.
- useEffect 블록에서 Flutter엔진을 실행한다.
- 원하는 엘리먼트에 렌더링 한다.
정리
블로그의 첫 게시물로 어떻게 Flutter 위젯을 Next.js 페이지에 렌더링 하는지 알아봤습니다. 웹에 익숙치 않다보니 꽤나 많은 시행착오를 겪었는데 글로 정리하니 별거 없어보이네요..ㅋㅋ 앞으로 다양한 주제로 자주 올릴수 있게 노력하겠습니다.