Hacker News

Native apps written in TypeScript and CSS

TypeScript와 CSS로 만드는 네이티브 앱

GeaStack은 TypeScript와 CSS로 작성한 앱을 여러 운영체제와 ESP32 같은 임베디드 기기에서 실행하는 도구 모음입니다. 중심에는 TypeScript를 C++로 정적으로 컴파일하는 컴파일러가 있으며, 저장소에는 앱 예제와 빌드·플래시 절차가 정리돼 있습니다.

AI 요약

GeaStack 예제 저장소에는 시뮬레이터, 임베디드 기기, GeaOS, Apple 플랫폼, VS Code·Cursor 확장에서 쓰는 앱과 도구가 모여 있습니다. 각 앱은 package.jsongea 매니페스트를 두고 ID, 진입점, 런타임, 지원 대상을 지정합니다. 예제 대부분은 TypeScript와 TSX로 작성하며, 웹과 ESP32, GeaOS 등 여러 대상을 지정할 수 있습니다.

개발 및 실행

개별 앱은 해당 폴더에서 npm install, npm run check, npm run build 명령으로 확인하고 빌드합니다. 테스트가 있는 앱은 폴더 안에서 npm test를 실행합니다. 웹 개발과 빌드는 별도 저장소인 시뮬레이터가 담당합니다. GEA_APPS_ROOT 환경 변수나 --app-dir 옵션으로 예제 저장소 또는 개별 앱 경로를 지정합니다. 두 저장소를 같은 디렉터리에 둘 필요는 없습니다. 지원 보드는 Gea CLI의 gea flash 명령으로 플래시하고, 필요하면 --monitor 옵션으로 모니터링합니다.

예제는 한 가지 동작을 분명하게 보여주도록 작게 유지하고, 대상별 임시방편보다 공용 프레임워크 API를 우선하라고 안내합니다. 복잡한 로직이나 물리·파싱 기능이 들어간 앱에는 테스트를 추가하고, 앱을 추가하거나 이름·지원 대상을 바꾸면 카탈로그 문서도 갱신해야 합니다.

컴파일 방식과 플랫폼

Hacker News에서 프로젝트 작성자는 Gea가 JavaScript 엔진이나 가상 머신을 실행하는 방식이 아니라 TypeScript를 C++로 정적 컴파일한다고 설명했습니다. 컴파일러는 호출 그래프를 분석해 타입을 추론합니다. anyunknown도 사용 형태가 구체적으로 좁혀지면 정적 타입으로 처리하고, 증명할 수 없는 경우에는 동적 값을 담는 박스형 표현으로 낮춥니다. JSON.parse(x) as T처럼 결과 타입을 지정하는 방법도 제공하며, 지정하지 않으면 해당 변수나 위치에서 동적 값 처리 비용을 부담합니다.

작성자 설명에 따르면 new Function은 C++로 만든 작은 평가기로 제한적으로 지원합니다. Fastify가 만드는 라우팅 함수에 쓰려고 구현했으며 조건문, 반복문, 변수 선언은 처리하지만 클래스, 비동기 처리, 구조 분해는 지원하지 않습니다. eval은 아직 지원하지 않는다고 밝혔습니다. Hono와 의존 패키지는 컴파일된다고 했고, Fastify와 MongoDB 네이티브 드라이버는 작업 중이라고 덧붙였습니다.

렌더링은 플랫폼별 네이티브 바인딩을 사용합니다. 작성자는 CSS, HTML Canvas API, JSX를 네이티브 구성 요소와 정렬로 렌더링한다고 설명했습니다. 그래픽 구현은 플랫폼마다 다릅니다. Apple 플랫폼은 UIKit·AppKit 뷰와 CoreGraphics, Windows는 GDI·GDI+, Linux는 SDL2의 선택을 따릅니다. Three.js 앱은 Apple 기기에서 Metal을 쓰고, Windows와 Xbox에서는 Direct3D 11을 지원하며 Direct3D 12는 상용 라이선스 대상이라고 설명했습니다. ESP32용으로는 브라우저 레이아웃 엔진을 축소한 형태의 2D 래스터라이저를 만들었다는 댓글도 있습니다.

작성자가 제시한 간단한 창의 메모리 사용량은 Mac에서 AppKit 16~17MB, Gea 18~20MB, Qt 25~35MB, Tauri 58~60MB입니다. Tauri는 WebKit 의존성 때문에 메모리를 더 쓴다는 설명도 덧붙였습니다. 숫자형은 기본적으로 double이며, 컴파일러가 범위를 증명하면 64비트 정수로 처리할 수 있습니다. 임베디드 성능을 위한 i32 같은 사용자 지정 타입과 타입 배열도 지원합니다. 일반 배열은 조밀하게 저장하지만 각 원소에 존재 비트를 둬 희소 배열의 빈 칸과 undefined를 구분합니다. 현재 Error.stack은 지원하지 않는다고 밝혔습니다.

라이선스

저장소는 MIT 라이선스이며, 이를 바탕으로 비공개 제품을 출시할 수 있습니다. 다만 임베디드 보드 지원 코드와 @geastack/chips는 GPL-3.0-only입니다. 이 코드로 비공개 펌웨어를 배포하려면 상용 라이선스가 필요하다고 안내합니다.

Hacker News 반응

  • @bertili — 처음 보면 잘 드러나지 않지만, 중심에는 TypeScript를 C++로 컴파일하는 컴파일러가 있고 여러 플랫폼 바인딩이 있습니다.
    • @cryptolobster — 북마크했습니다. 감사합니다.
  • @sublinear — 아, 임베디드 앱용이군요. 꽤 괜찮네요.
    • @dashersw — iOS, macOS, Android, Windows, Linux도 지원합니다. Three.js 게임은 Xbox에서도 실행하며, 일부 Node.js 앱도 지원합니다.
    • @jr3592 — 웹용 React 프로젝트를 넣으면 네이티브 macOS 프로젝트를 만들 수 있나요?
    • @letrix — React Native와 더 비슷해 보입니다. [예제](https://github.com/geastack/examples/tree/main/apps/notes-na...)와 [영상](https://www.youtube.com/watch?v=o5RDfAmzE7s)을 참고하세요.
    • @dashersw — Gea 프런트엔드(geajs.com)를 지원합니다. 플러그인을 만들면 React도 컴파일할 수 있겠지만, 그 작업은 커뮤니티 몫으로 남겨뒀습니다.
  • @zerr — 그래픽과 오디오는 무엇을 사용하나요?
    • @dashersw — 플랫폼마다 네이티브 바인딩을 씁니다. 그래픽은 CSS, HTML Canvas API, JSX로 작성합니다. 렌더러가 네이티브 구성 요소와 정렬을 처리하고, Canvas API는 플랫폼별 네이티브 표면으로 바꿉니다.
    • @zerr — OpenGL, Vulkan, Metal 중 무엇을 쓰는지 묻는 겁니다.
    • @dashersw — 플랫폼 기본 구현을 따릅니다. Android는 컴포지터, Apple은 UIKit·AppKit 뷰와 CoreGraphics, Windows는 GDI·GDI+, Linux는 SDL2가 선택하는 구현을 씁니다. Three.js 앱은 iOS·macOS에서 Metal을 쓰고, Windows·Xbox에서는 Direct3D 11을 씁니다. Direct3D 12는 상용 라이선스 대상입니다.
    • @zero_shift — 처음에는 분명하지 않지만, JavaScript로 작성한 앱과 운영체제별 네이티브 바인딩 저장소를 보면 임베디드 JavaScript 엔진이 운영체제 위젯을 호출하는 방식 같네요.
    • @dashersw — TypeScript를 C++로 정적 컴파일합니다. JavaScript 엔진이나 가상 머신은 실행하지 않습니다.
    • @trelliscoded — 큰 플랫폼에서 OpenGL을 쓴다는 언급은 봤는데 ESP32에서는 어떻게 동작하는지 궁금했습니다. ESP32 전용 2D 래스터라이저를 만들었더군요. 브라우저 레이아웃 엔진을 작게 만든 것과 비슷합니다.
  • @slopinthebag — AI를 너무 많이 쓴 것처럼 보여서 실체 없는 프로젝트 같네요. 실제로 작동하는지 확인한 사람이 있나요?
    • @dashersw — 데모 영상과 코드가 있습니다. CSS로 3D 큐브를 그리는 첫 영상의 코드도 공개했습니다. CLI 명령 몇 개면 실행됩니다. 저는 WaveShare ESP32-S3 AMOLED Touch 2.06인치 기기를 쓰고 있지만, 같은 코드가 Gea 컴파일 대상에서 모두 렌더링됩니다.
    • @dspillett — AI로 만든 것처럼 보이는 프로젝트의 걱정거리는 현재 상태가 작동하느냐만이 아닙니다. 현재 상태가 끝까지 그대로라면, 사용자가 죽은 프로젝트에 의존하다 직접 유지보수해야 할 수 있습니다.
    • @dashersw — 우려를 이해하고 고맙게 생각합니다. Gea Stack은 약 1년 동안 작업했고, 현재 기능은 지난 6개월간 개발했습니다. Gea를 중심으로 회사를 만드는 중이며 임베디드 기기 업체, 개발 파트너, 고객과 협력하고 있습니다. 하드웨어를 사면 직접 펌웨어를 올릴 수 있어야 한다는 ‘펌웨어 자유’를 지향합니다. TypeScript, JSX, CSS는 AI가 잘 아는 언어이기도 합니다.
    • @slopinthebag — AI 프로젝트는 데모만 작동하는 경우가 많습니다. 무례하게 말하려는 건 아니고, 직접 시험할 수 없는 상황이라 궁금합니다. 동적인 JavaScript를 어떻게 C++로 컴파일하나요? 바이트코드로 바꾸나요, 아니면 언어 일부만 지원하나요?
    • @dashersw — 컴파일러를 만드는 데 6개월이 걸렸습니다. 먼저 타입이 엄격한 TypeScript부터 시작했습니다. unknownany가 있는 프로젝트에는 동적 대체 처리를 추가했고, 이후 타입 추론 구조도 몇 차례 바꿨습니다. any로 선언했더라도 실제 사용이 한 가지 형태라면 정적 구조체로 만들 수 있습니다. 바이트코드는 쓰지 않으며 모든 동적 언어 기능을 지원하지는 않지만, Hono와 의존 패키지는 컴파일됩니다.
  • @realysy — 간단한 Hello World 창은 메모리를 얼마나 쓰나요? Qt, Tauri와 비교하면요?
    • @dashersw — Mac 기준으로 평범한 AppKit 앱은 16~17MB, Gea는 18~20MB, Qt는 QML 사용 여부에 따라 25~35MB, Tauri는 58~60MB입니다. Tauri는 WebKit 의존성이 크고 UI도 네이티브가 아니라 WebKit으로 HTML을 렌더링합니다.
    • @hombre_fatal — 비교 데이터 하나를 더하면, 제가 마지막으로 확인했을 때 GTK4의 hello.c는 100MB 넘게 썼습니다.
  • @jeswin — 비슷한 도구인 tsonic.org를 만드는 사람입니다. 지원하지 않는 TypeScript 기능을 문서로 분명히 알려달라는 의견을 자주 듣습니다. eval("...")이나 unknown, any처럼 지원하기 어려운 부분이 무엇인지 알아야 사용자가 피할 수 있습니다.
    • @dashersw — anyunknown은 호출 그래프를 따라 구체적인 타입을 증명하거나 사용 형태가 하나뿐이면 정적으로 처리합니다. 증명할 수 없으면 박스형 동적 값으로 낮춥니다. JSON.parse(x) as T로 타입을 지정할 수도 있습니다. new Function은 제한적으로 지원하며, 구체적인 제약은 문서에 적었습니다.
    • @jeswin — 사용자 입장에서는 지원 경계를 모르는 일이 가장 어렵습니다. npm 라이브러리를 가져다 쓰면 더 그렇습니다. Microsoft의 TypeScript 컴파일러처럼 규모가 큰 프로젝트로 호환성을 시험하면 문제를 많이 찾을 수 있습니다.
    • @dashersw — 말씀하신 제한은 일반 TypeScript가 아니라 eval 쪽의 제한입니다. Hono를 포함해 많은 npm 라이브러리가 컴파일됩니다. Fastify와 MongoDB 네이티브 드라이버도 작업 중입니다.
    • @jeswin — number는 어떻게 처리하나요? i32, i64, double, float, i16은 성능 특성이 다릅니다. 희소 배열이나 Error.stack도 궁금합니다.
    • @dashersw — 기본 number는 double이고, 크기가 적절하다고 증명되면 64비트 정수로 처리할 수 있습니다. Node와 동작을 맞추는 것이 목표라 반복문의 카운터 등은 double로 둡니다. 임베디드 성능에 필요한 i32 같은 사용자 지정 타입과 타입 배열도 지원합니다. 일반 배열에는 원소마다 존재 비트를 둬 희소 배열의 빈 칸과 undefined를 구별합니다. Error.stack은 아직 지원하지 않습니다.

원문: GitHub / 번역·요약: Trawling