Cloudflare는 Workers 런타임 환경의 핵심 구성 요소인 오픈 소스 구성 요소 workerd의 모듈 레지스트리를 다시 작성했습니다. 목표는 속도와 표준 준수성을 개선하고 모듈을 로드하고 해결하는 방식을 Node.js의 동작 방식에 더 가깝게 만드는 것입니다. 이는 Workers에서 안정적인 Node.js API를 기본적으로 활성화하고, 압축된 패키지 크기 제한을 제거한 뒤 모든 요금제에서 최대 64MiB 크기의 애플리케이션을 배포할 수 있도록 한 것과 동시에 진행됩니다.
그러나 가장 중요한 변화는 사용 가능한 API 수에만 관한 것이 아닙니다. Node.js 애플리케이션은 런타임이 모듈을 식별하고 로드하며 캐시하는 방식에도 의존합니다. 여기에는 ESM, CommonJS, WebAssembly 모듈이 포함되며, 이러한 책임을 workerd 내부의 모듈 레지스트리가 담당합니다.
새 레지스트리에서 무엇이 바뀌었나?
개발자는 Worker 설정에 new_module_registry 플래그를 추가해 새 구현을 시험할 수 있습니다. 이를 활성화하면 Workers는 import.meta.url, import.meta.main, import.meta.resolve()를 모두 지원하며, 쿼리 문자열과 프래그먼트를 포함해 모듈 지정자를 실제 URL로 취급합니다.
실제로 이는 상대 경로 import가 new URL()과 동일한 규칙을 따르고, 완전한 URL을 모듈 지정자로 사용할 수 있음을 의미합니다. 또한 쿼리 문자열이나 URL 프래그먼트가 다른 모듈은 동일한 소스를 가리키더라도 서로 독립적인 모듈이 됩니다. 따라서 동일한 파일의 두 버전이 서로 분리된 최상위 상태를 가질 수 있으며, 동일한 지정자를 재사용하면 동일한 모듈 인스턴스가 반환됩니다.
레지스트리는 import 속성도 올바르게 검증합니다. 현재 사용할 수 있는 유형은 json이며, text와 bytes 같은 유형은 정의되어 있지만 아직 활성화되지 않았기 때문에 명확한 오류 메시지와 함께 거부됩니다. 또한 이제 인식할 수 없는 속성은 조용히 무시되지 않고 명시적인 오류를 발생시킵니다.
향상된 Node.js 호환성
ESM 모듈을 로드할 때 require()는 require(esm)에 관한 Node.js 규칙을 따릅니다. 모듈이 module.exports라는 이름으로 값을 내보내면 해당 값이 반환되고, 그렇지 않으면 호출은 모듈의 네임스페이스 객체를 반환합니다. 다만 workerd에 내장된 node: 구성 요소는 예외로, 개발자가 default 속성에 접근하도록 강제하는 대신 예상되는 CommonJS 인터페이스를 반환합니다.
중요한 제한 사항도 있습니다. require()는 top-level await를 포함하거나 그러한 모듈에 의존하는 모듈을 로드할 수 없습니다. require는 결과를 동기적으로 반환해야 하기 때문입니다. 이 경우 Workers는 오류를 발생시키며, 비동기적인 import()가 적절한 경로입니다. 모듈 자체가 이전에 import()를 통해 로드되었더라도 이 규칙은 계속 적용됩니다.
새 버전은 정적 import, 동적 import(), require() 중 어떤 방식으로 로드하든 오류 유형과 메시지 형식도 통일합니다. 모듈을 찾지 못하면 일반 오류가 반환되고, URL로 분석할 수 없는 지정자는 TypeError를 발생시킵니다. 이러한 일관성은 사용자 지정 로더나 재시도 로직을 구축하는 개발자에게 유용합니다.
개발자에게 실제로 무엇이 달라지나?
이전에는 Wrangler와 같은 Workers 도구가 대부분의 애플리케이션 파일과 종속성을 esbuild를 사용해 하나의 모듈로 묶었고, 그 결과 런타임이 처리해야 하는 그래프의 크기가 줄어들었습니다. Cloudflare Vite 플러그인을 사용하는 경우 Vite 8에서는 Rolldown을 사용해 진입 모듈을 생성하고, 코드 분할 시 동적으로 로드되는 모듈과 같은 추가 모듈을 생성합니다.
새 레지스트리를 통해 번들러는 변환을 줄이고 모듈 해결을 런타임에 더 많이 의존할 수 있습니다. 이는 애플리케이션을 여러 모듈로 배포하거나 --no-bundle 옵션을 사용하거나 Wasm 파일, 텍스트, 바이너리 파일을 하나의 번들 안에 포함하는 대신 독립적인 파일로 처리할 때 특히 중요합니다.
또한 새 구현은 정적 또는 동적 import를 통해 모듈이 처음 가져와질 때까지 컴파일을 지연하며, 동일한 Worker를 실행하는 여러 V8 isolate 인스턴스 사이에서 코드 캐시를 공유할 수 있도록 합니다. Cloudflare에 따르면 이는 이전 구현에서 발생하던 반복적인 컴파일과 메모리에 소스의 여러 복사본이 존재하는 문제의 한 측면을 해결합니다.
제한 사항과 주의할 점
이러한 변경에도 불구하고 새 레지스트리는 사용 중인 호환성 날짜와 관계없이 기존 Worker나 신규 Worker에 자동으로 활성화되지 않습니다. 플래그를 명시적으로 추가해야 하며, Cloudflare는 이전 구현도 계속 사용 가능하게 유지했습니다. Cloudflare는 배포된 Workers가 계속 기존과 같이 작동할 것이라고 밝혔습니다.
새 구현은 WebAssembly 모듈의 소스 단계 import도 지원하며, 이 경우 WebAssembly.Module 객체를 직접 반환합니다. 그러나 현재 이 기능은 WebAssembly에서만 작동하고 다른 유형은 구문 오류를 발생시킵니다. 따라서 이번 업데이트는 모든 모듈 로드 경로에 제한 없이 포괄적으로 적용되는 전환이 아니라, 개발자가 점진적으로 시험할 수 있는 더욱 호환성 높은 기반을 제공합니다.
편집자 해설: 이번 변화의 실질적인 가치는 Workers를 모듈 동작의 부분적인 모방에서 JavaScript 및 Node.js 표준에 더 가까운 모델로 옮긴 데 있습니다. 이는 빌드 도구가 요구하는 변환을 줄이고 다중 모듈 애플리케이션의 이식성을 높일 수 있습니다. 그러나 최종적인 영향은 개발자가 새 플래그를 어떻게 시험하느냐에 달려 있으며, 특히 require(), top-level await 또는 import 속성을 사용하는 종속성에서 그러합니다. 또한 기본적으로 활성화되지 않는다는 점은 향상된 호환성이 아직 모든 애플리케이션의 일반적인 동작이 아니라는 것을 의미합니다.