GN⁺: Figma의 TypeScript 전환 여정
(figma.com)타입스크립트로의 Figma 여정: 사용자 정의 프로그래밍 언어를 컴파일하여 제거
- Figma는 모바일 렌더링 아키텍처의 핵심 부분을 Skew라는 사용자 정의 프로그래밍 언어로 작성해왔음
- 재생 엔진에서 추가적인 성능을 얻기 위해 발명한 언어
- 개발 단 하루도 중단하지 않고 Skew를 자동으로 TypeScript로 마이그레이션한 방법을 소개
Skew 언어의 시작과 한계
- Skew는 Figma 초기에 부수적인 프로젝트로 시작됨
- 당시 웹과 모바일 둘 다에서 지원되는 프로토타입 뷰어를 빠르게 구축할 필요가 있었음
- 더 발전된 최적화와 더 빠른 컴파일 시간을 가능하게 하는 전체 컴파일 대상 자바스크립트 프로그래밍 언어가 됨
- 시간이 지나면서 Skew 코드가 누적되면서 한계점이 드러남
- 신입사원이 적응하기 어려움
- 나머지 코드베이스와 쉽게 통합할 수 없음
- Figma 외부에 개발자 생태계가 부족함
- 확장의 어려움이 원래의 장점을 능가하게 됨
TypeScript로의 마이그레이션을 가능하게 한 요인들
- 모바일 브라우저에서 WebAssembly 지원 확대
- Skew 엔진의 핵심 구성요소를 C++ 엔진의 해당 구성요소로 교체
- TypeScript로 이동해도 성능 손실이 적어짐
- 팀의 성장으로 개발자 경험에 집중할 수 있는 리소스 할당 가능
코드베이스 변환 과정
- 목표: Skew 코드 전체를 TypeScript로 변환
- 수동으로 다시 작성하는 대신 자동화된 마이그레이션 선택
- 개발 속도 저하 방지 & 사용자에 대한 런타임 오류 및 성능 저하 방지
- 3단계 롤아웃 프로세스
- Skew 작성, Skew 빌드
- 원래 빌드 프로세스 유지, 트랜스파일러 개발, TS 코드를 GitHub에 체크인
- Skew 작성, TypeScript 빌드
- TypeScript 코드베이스에서 직접 프로덕션 트래픽 롤아웃 시작
- 개발자는 여전히 Skew 작성, 트랜스파일러가 Skew를 TS로 변환
- TypeScript 작성, TypeScript 빌드
- 개발을 위해 TS 코드를 진실의 원천으로 만들 필요
- 자동 생성 프로세스를 차단하고 Skew 코드를 코드베이스에서 삭제
- Skew 작성, Skew 빌드
트랜스파일러 작업에 대한 참고사항
- 컴파일러는 프론트엔드와 백엔드로 구성
- 프론트엔드: 입력 코드 구문 분석 및 이해, 유형 검사, 구문 검사 수행
- IR(중간 표현)로 변환 - 원래 입력 코드의 의미론과 논리 완전히 캡처
- 백엔드: IR을 다양한 언어로 변환
- 트랜스파일러: 사람이 읽을 수 있는 코드를 생성하는 특수한 유형의 컴파일러
마이그레이션 과정에서 직면한 문제들
- 배열 destructuring의 성능 문제
- 자바스크립트 배열 destructuring을 사용하지 않으면 최대 25%의 성능 향상
- Skew의 "devirtualization" 최적화
- 롤아웃 과정에서 추가 단계를 거쳐 devirtualization이 코드베이스 동작을 깨뜨리지 않도록 함
- TypeScript에서는 초기화 순서가 중요
- 트랜스파일러가 이 순서를 존중하는 코드를 생성해야 함
개발자 경험을 위한 Source Map 활용 사례
- 개발자 생산성을 위해 마이그레이션을 쉽게 하고, 디버깅 경험을 원활하게 하는데 집중
- 소스 맵을 이용해 컴파일된 코드와 소스 코드 연결
- 브라우저는 자바스크립트만 이해할 수 있음
- 소스 맵을 통해 브라우저가 컴파일된 자바스크립트 번들에서 소스 코드의 중단점을 어디에서 멈춰야 하는지 알 수 있음
- 3단계 프로세스에서 소스 맵 생성
- TypeScript → JavaScript 소스 맵 생성
- 각 Skew 소스 파일에 대한 Skew → TypeScript 소스 맵 생성
- 소스 맵을 구성하여 Skew에서 JavaScript로 매핑 생성
조건부 컴파일 처리 사례
- Skew에서는 최상위 "if" 문으로 조건부 코드 컴파일 허용
- 컴파일 시간 상수를 사용해 조건 지정
- 동일한 코드베이스의 다른 빌드 타겟 정의 가능
- TypeScript에는 조건부 컴파일 기능 없음
- 번들링 단계에서 수행하도록 변경
- esbuild의 "defines" 및 데드 코드 제거 기능 사용
- 번들 크기가 약간 증가하는 부작용 발생
TypeScript 시대의 프로토타이핑 개발
- Skew 코드를 TypeScript로 마이그레이션함으로써 Figma의 핵심 코드베이스 현대화
- 내부 및 외부 코드와 훨씬 쉽게 통합할 수 있는 길을 열어줌
- 개발자들이 더 효율적으로 작업할 수 있게 됨
- 당시에는 TypeScript가 적합하지 않았지만 지금은 확실히 적합한 선택
- TypeScript로 이동의 모든 이점을 누리기 위한 후속 작업 진행 중
- 나머지 코드베이스와의 통합
- 훨씬 쉬워진 패키지 관리
- TypeScript 생태계의 새로운 기능 직접 사용 등
GN⁺의 의견
-
Figma는 사용자 정의 프로그래밍 언어인 Skew에서 TypeScript로 전환하는 과정을 매우 체계적이고 단계적으로 진행함. 개발 중단 없이 자동화된 마이그레이션을 수행한 것이 인상적임. 회사 규모가 커지면서 생기는 기술 부채를 해결하고 생태계 변화에 발맞춘 좋은 사례라고 생각함.
-
성능에 집중한 사용자 정의 언어에서 범용 언어로 전환하는 과정에서 WebAssembly 등장 등 기술 환경의 변화가 중요한 역할을 함. 기술 선택시 당장의 필요에 의한 선택도 중요하지만, 기술 발전 속도와 방향을 고려하는 것이 필요함을 시사.
-
개발자 경험을 고려한 소스맵 활용, 조건부 컴파일 처리 등 실무에 참고할만한 디테일한 방법들이 소개되어 있어 좋음. 기존 레거시와 호환을 유지하면서 점진적으로 마이그레이션하는 과정이 인상깊음.
-
대규모 코드베이스에서 이런 작업을 위해서는 자동화된 코드 변환기 개발이 필수적일 것 같음. Skew 컴파일러를 활용한 트랜스파일러 개발이 핵심이었던 것으로 보임. 컴파일러 이론과 내부 구현에 대한 전문성이 요구되는 작업으로 보임.
-
프로그래밍 언어 마이그레이션은 단순 코드 변환 그 이상의 영향을 미침. 개발 문화와 생태계 전반에 긍정적인 변화를 가져올 수 있음. 신중한 접근이 필요하지만 개발 조직의 역량이 된다면 도전해볼만한 일이라 생각함.
Hacker News 의견
-
Andrew Chan, 프로젝트 참여자에 따르면 Figma는 거의 10년 동안 다른 부분에서 TypeScript를 사용해왔고, 대부분의 기간 동안 Skew보다 TypeScript가 더 많았음. Skew는 모바일 엔진과 프로토타이핑 플레이어, 미러링 기능 등 일부 제품 영역에서 사용되었음.
-
Figma가 JS를 위한 사용자 정의 언어를 가지고 있었다는 점이 놀라웠고, 그것이 TS보다 빨랐다는 점은 더욱 놀라웠음. 그런데 나중에 더 느린 TS로 마이그레이션함.
-
Evan Wallace(전 Figma CTO)에 따르면 Skew는 더 엄격한 타입 시스템으로 인해 가능해진 더 나은 최적화 덕분에 TypeScript보다 1.5~2배 빨랐음.
-
배열 구조 분해 할당 시 JavaScript가 배열에서 직접 인덱싱하는 대신 배열을 반복하는 이터레이터를 구성하는 것이 흥미로움. JS가 배열을 직접 인덱싱하지 않는 이유가 궁금함.
-
Skew는 콜백만 있었던 것으로 보임. async/await와 같은 최신 JavaScript 기능과 더 유연한 타입 시스템이 언급됨.
-
Figma는 사용자 정의 TypeScript DSL + 컴파일러를 작성하여 권한 문제와 같은 보안 문제를 해결했음.
-
대기업마다 자체 내부 도구, 언어, 쿠버네티스를 가지고 있는데 공유하지 않는 것이 안타까움. Skew가 오픈소스였다면 더 나은 TypeScript가 되었을 수도 있음.
-
Figma의 WebAssembly 사용 동기가 궁금함.
-
배울 교훈: 사용자 정의 언어를 만들지 말 것.
-
TypeScript에 반대하는 사람들의 의견을 보면 흥미로움. TypeScript는 거의 모든 코드 라인을 개선하는 단점이 거의 없는 도구임. 그들은 새로운 것을 배우기 두려워하거나, 시간을 할애하지 않으려 하거나, 유용성을 오해하고 있는 것으로 보임. TypeScript 반대자들에 동의한다면 그 이유에 대해 더 깊이 생각해 볼 필요가 있음. 그렇지 않으면 큰 불이익을 감수해야 함.