zerovoids.dev
zerovoids.dev

Ø NEWS

엣지 런타임 · 오픈소스

클라우드플레어, 워커스 모듈 레지스트리를 URL 기반으로 다시 짰다

클라우드플레어가 워커스 런타임(workerd)의 모듈 레지스트리를 다시 짜, 지정자를 URL로 다루고 지연 컴파일과 캐시 공유를 설계에 넣었다. new_module_registry 플래그를 켜면 Node.js 모듈 해석 규칙에 훨씬 가까워진다.

키워드nodejavascriptesminfrabuild-tools

클라우드플레어 워커스에 올리는 코드는 원래 여러 파일과 의존성으로 나뉘어 있지만, 랭글러가 배포 전에 esbuild로 이걸 통째로 이어붙인다. 그렇게 만들어진 번들 하나가 수십만 줄까지 불어난 사례도 있었다. 클라우드플레어는 이 방식의 밑바탕이었던 워커 런타임(workerd)의 모듈 레지스트리 자체를 다시 짰다.

왜 다시 짜야 했나

기존 레지스트리는 모듈 지정자를 URL이 아니라 파일시스템 경로처럼 해석했다. 그래서 import.meta.url을 깔끔하게 구현할 방법이 없었고, 상대 경로 임포트는 new URL()과 다른 규칙을 따랐으며, node: 와 cloudflare: 같은 프로토콜도 진짜 프로토콜이 아니라 문자열 접두사로 특별 취급됐다. 여기에 더해 워커 번들 전체를 실제로 임포트되는지와 상관없이 미리 다 컴파일했고, V8 아이솔레이트마다 그 코드를 따로 한 벌씩 갖고 있었다. 클라우드플레어는 CPU 코어에 부하를 나누려고 같은 워커를 여러 V8 아이솔레이트 복제본으로 띄우는데, 그 결과 같은 소스를 몇 번이고 다시 컴파일하고 메모리에도 여러 벌 올려두는 셈이었다.

새 레지스트리는 지정자를 처음부터 URL로 다루고, 지연 컴파일과 캐시 공유를 설계 단계부터 넣었다.

지금 뭐가 달라지나

new_module_registry 호환 플래그를 켜면 import.meta.url, import.meta.main, import.meta.resolve()가 모두 동작한다. node: 내장 모듈은 어떤 경로로 불러오든 같은 모듈 인스턴스로 해석되고, with { type: 'json' } 같은 임포트 속성도 제대로 검증된다. require()로 ES 모듈을 불러올 때는 Node.js의 require(esm) 규칙을 그대로 따르고, 에러는 어느 경로로 실패했든 같은 클래스와 메시지를 쓴다. 모듈은 처음 임포트될 때 지연 컴파일되고, WebAssembly 모듈은 소스 페이즈 임포트도 지원한다. 덕분에 클라우드플레어 Vite 플러그인처럼 Rolldown으로 번들링하는 도구가 변형을 덜 하고 모듈 해석을 런타임에 더 맡길 길이 열렸다. --no-bundle로 배포하거나 여러 모듈을 그대로 올리면 작성한 그대로의 그래프가 런타임에 나타난다.

기존 레지스트리 구현이 당장 없어지는 건 아니다. 다만 이번 변화로 워커스는 Node.js가 모듈을 찾고 불러오고 캐싱하는 방식에 한층 가까워졌고, 랭글러나 Vite 같은 번들러가 배포 전에 코드를 미리 변형해야 할 이유도 그만큼 줄었다.