본문으로 건너뛰기

© 2026 Molayo

CSS-T헤드라인2026. 05. 25. 07:04

곧 우리는 JavaScript를 ShadowRealm으로 마침내 추방할 수 있게 될 것입니다

요약

JavaScript의 새로운 표준 제안인 ShadowRealm에 대해 설명합니다. ShadowRealm은 각기 다른 전역 환경을 가진 독립적인 실행 컨텍스트를 제공하여, 기존 JavaScript의 고질적인 문제인 전역 스코프 오염을 방지하고 격리된 실행 환경을 구축할 수 있게 합니다.

핵심 포인트

  • ShadowRealm은 독립적인 전역 환경을 가진 새로운 JavaScript 렐름을 제공함
  • 기존의 전역 스코프 오염 문제를 해결할 수 있는 격리된 실행 환경 구축 가능
  • JavaScript 애플리케이션은 멀티 스레드 환경(Web Workers 등)을 통해 다중 렐름 활용 가능

이 내용을 평정심을 유지하며 전달하기란 정말 쉽지 않을 것 같습니다. 좋아요, 할 수 있습니다. 저는 _전문 기술 작가(professional technical writer)_니까요. 무표정하게, 비즈니스 모드로. 에헴: 만약 여러분이 TC39(JavaScript의 표준을 유지하고 개발하는 책임을 지는 표준화 기구)에서 진행 중인 작업을 계속 지켜봐 오셨다면, ShadowRealms에 관한 그들의 최근 작업snrk. 죄송합니다! 죄송해요! 괜찮습니다! 휴— "ShadowRealms"라니, 이름이 정말 대단하네요. 좋아요, 잠시만요, 처음부터 다시 시작해 볼게요. 그러면 도움이 될지도 모르겠습니다.

여러분은 언젠가 JavaScript가 "싱글 스레드(single-threaded)"라고 설명되는 것을 보았을 가능성이 매우 높습니다. 이는 보통 "대소문자 구분(case sensitive)", "공백 무시(whitespace insensitive)", 그리고 "수학에 약함"과 함께 JavaScript의 기초 목록 상단에 위치하곤 합니다. 엄격한 "컴퓨터 과학(computer science)"적 관점에서는 그것이 정확한 표현이지만, 저는 그것을 볼 때마다 여전히 조금씩 신경이 쓰입니다.

제 말은, JavaScript가 멀티 스레드(multi-threaded)가 아니라는 점에서는 확실히 정확하다는 뜻입니다. 스크립트는 항상 매우 선형적인 방식으로 실행됩니다. 위에서 아래로, 왼쪽에서 오른쪽으로, 하나의 실행 컨텍스트(execution context)가 지나가면 다음 컨텍스트가 이어지며, 콜 스택(call stack)을 쌓아 올렸다가 다시 내려오는 방식이죠. 다만 여러분은 결국 Web Workers와 같은 개념을 배우게 될 텐데, 이는— 너무 세세하게 짚고 넘어가려는 건 아니지만— _다른 스레드(another thread)_에서 JavaScript 코드를 실행할 수 있게 해줍니다. 바로 이 지점에서 "JavaScript는 싱글 스레드이다"라는 프레임워크가 덜 유용해진다고 생각합니다. 왜냐하면 JavaScript가 멀티 스레드 _언어(language)_는 아닐지라도, JavaScript _애플리케이션(application)_은 여러 개의 스레드를 사용할 수 있기 때문입니다.

JavaScript **렐름 (realm)**이 싱글 스레드(single-threaded)라고 말하는 것이 더 적절한 프레임워크이며, 기술적으로도 똑같이 정확한 표현입니다. 렐름(realm)은 코드가 실행되는 환경을 의미합니다. 브라우저 탭은 하나의 렐름이며, 그 렐름 내부에는 JavaScript가 실행되는 단일 스레드인 **메인 스레드 (main thread)**가 존재합니다. Web Worker는 **워커 스레드 (worker thread)**를 가진 렐름입니다. 교차 출처(cross-origin) iframe에서 실행되는 JavaScript는 해당 iframe 렐름의 메인 스레드에서 실행됩니다. 예를 들어, 우리는 단일 함수의 실행을 다른 스레드로 오프로드(offload)할 수 없습니다. JavaScript는 언어 자체로서 싱글 스레드이기 때문입니다. 하지만 JavaScript 애플리케이션은 여러 개의 렐름에 걸쳐 존재하며 여러 실행 스레드를 사용할 수 있고, 각 렐름은 특정 방식으로 다른 렐름과 통신할 수 있습니다.

각 JavaScript 렐름은 자신만의 전역 환경(global environment)을 가집니다. 브라우저 탭에서 전역 객체(global object)는 Window 인터페이스입니다. 해당 브라우저 탭 내의 동일 출처가 아닌(non-same-origin) iframe에서도 마찬가지입니다. 전역 객체는 해당 iframe

태초부터(90년대부터) 이 언어를 괴롭혀온 전역 스코프 오염 (global scope pollution)을 고려할 때, 이미 전역 스코프를 어지럽히고 있는 것들로부터 영향을 받지 않거나, 반대로 영향을 주지 않도록 JavaScript 실행을 위한 샌드박스 (sandbox) 역할을 할 수 있는 별도의 렐름 (realm)으로 코드를 오프로딩 (offloading)하는 유스케이스 (use case)를 상상하는 것은 어렵지 않습니다. 우리는 테스트 스위트 (test suite)의 일부를 '클린 룸 (clean room)'에서 실행하고 싶을 수도 있습니다. 그곳에서는 테스트를 수행하는 행위 자체가 테스트 결과에 잠재적으로 간섭할 수 없으며, 모의 데이터 (mock data)가 실제 데이터와 충돌하지 않을 수 있습니다. 또는, 전역 환경에 접근할 필요가 없는 서드파티 라이브러리 (third-party libraries)가 아무런 이득 없이 전역 환경을 어지럽히는 것을 방지하기 위해, JavaScript 애플리케이션 자체가 포함된 렐름으로부터 격리하고 싶은 코드를 실행할 장소가 필요할 수도 있습니다.

현재 상태의 렐름 (realms)으로는 그것을 할 수 없습니다. 기억하세요, JavaScript는 각 렐름 (realm)이 싱글 스레드 (single-threaded)라는 점에서 싱글 스레드이며, 이러한 스레드 간의 통신은 제한적입니다. 유스케이스 (use case)가 부정할 수 없이 확실함에도 불구하고, 우리는 별도의 렐름 (realm)을 사용하여 코드의 단일 실행 스레드에서 코드를 실행한 다음, 그 실행 결과를 기본 렐름 (realm)의 메인 스레드로 다시 엮어 넣을 수 없습니다. 그것은 정의상 멀티 스레드 실행 (multi-threaded execution)이며, 이는 단순히 JavaScript의 근본적인 성격에 반할 뿐만 아니라, 음, 이렇게 표현하겠습니다: JavaScript가 동시에 여러 실행 스레드를 허용하는 것은 우리에게 새로운 문제들을 안겨줄 것입니다.

이런 방식으로 코드를 오프로드(offload)하려면 새로운 종류의 렐름(realm)이 필요할 것입니다. 즉, 자신만의 전역 객체(global object)와 내장 객체(intrinsic objects)를 가지되, 자신만의 스레드(thread)는 가지지 않는 렐름 말입니다. 이곳으로 오프로드된 코드는 해당 스크립트를 "소유한" 렐름의 메인 스레드에서 여전히 실행될 것입니다. 우리 렐름의 어두운 투영(dark reflection); 빛이 결코 닿을 수 없는 렐름, 우리가 추방한 코드의 덧없고 일시적인 그림자만이 거주할 수 있는 곳 말이죠! 멀리서 천둥이 울리는 소리를 상상해 보세요. 제가 망토를 두르고 있거나, 와인 잔을 바닥에 내던지는 모습도 상상해 보셔도 좋습니다. 아시잖아요, 분위기를 좀 내보라고요. 안 그럴 수 있겠습니까? 제 말은, 이것들의 이름이 바로 다음과 같다는 겁니다:

ShadowRealms

제안된 ShadowRealm API는 오직 "격리(isolation)"만을 위해 특별히 설계된 새로운 종류의 렐름을 도입합니다. ShadowRealm은 자신만의 실행 컨텍스트(execution context)를 가지지 않습니다. ShadowRealm으로 오프로드된 코드는 자신만의 전역 객체와 내장 객체를 가진 의사 렐름(pseudo-realm)에 존재하게 됩니다. 해당 코드는 ShadowRealm이 생성된 코드와 동일한 스레드에서 계속 실행됩니다. 즉, 우리는 제한된 방식으로 두 개의 분리된 스레드 사이에서 자원을 주고받으며 통신해야 하는 상황에 강제되지 않습니다. 요약하자면, 스크립트는 단일 렐름에 국한되었을 때와 같은 방식으로 실행되지만, 외부 렐름의 내장 객체, API, 전역 객체, 그리고 우리 스크립트가 해당 전역 객체에 수행한 그 어떤 작업으로부터도 격리(quarantine)됩니다.

복잡하게 들릴 수도 있지만, 제안된 API는 실제로는 매우 단순할 것입니다:

// ShadowRealm 생성:
const shadow = new ShadowRealm();

...

참고: 이 코드는 여전히 이론적인 단계라는 점을 유념하세요. 아직 ES-262 표준이나 브라우저에 존재하지 않습니다.

globalFunction은 앞서 보았던 것처럼 외부 렐름의 전역 객체에 정의되지만, 새로 생성된 ShadowRealm 내부의 전역 객체에는 정의되지 않습니다. 우리가 ShadowRealm "외부"에서 무엇을 하든, 해당 ShadowRealm의 전역 객체는 깨끗한 상태로 유지됩니다. 당연히 그 반대의 경우도 마찬가지입니다:

우리는 ShadowRealm 내부에서 해당 함수를 선언했으며, 그 ShadowRealm 객체를 참조하는 변수를 통해 호출할 수 있습니다. 해당 함수는 외부의 전역 객체(global object) 및 다른 ShadowRealm의 전역 객체로부터 격리된 상태를 유지합니다:

// ShadowRealm 생성:
const firstShadow = new ShadowRealm();
const secondShadow = new ShadowRealm();
...

어느 정도까지는 "격리(Quarantined)"된 셈입니다. ShadowRealm이 진정한 보안 경계(security boundary)를 제공하는 것은 아닙니다. ShadowRealm 내부에서 실행되는 코드가 다른 렐름(realm)에서 실행되는 코드에 대해 추론(inferences)을 할 수 있기 때문입니다. 대신 ShadowRealm은 무결성(integrity) 경계라고 생각할 수 있습니다. 즉, 우리가 허용하지 않는 한 ShadowRealm 내부에서 실행되는 코드가 다른 렐름을 직접적으로 방해할 수 없다는 점에서는 그렇습니다. ShadowRealm으로 밀려난 코드가 외부의 객체들을 방해할 수는 없지만, 우리는 여전히 해당 작업의 결과물을 호스트 렐름(host realm)에서 동일한 작업을 수행했을 때와 같은 방식으로 자유롭게 사용할 수 있습니다:

// ShadowRealm 생성:
const shadow = new ShadowRealm();

...

무한히 생성 가능한 일회용 클린룸(cleanrooms)! 우리가 원하는 어떤 코드든 실행할 수 있으면서도, 그 코드가 다른 ShadowRealm이나 외부 렐름(소위 "빛의 렐름(light realm)")의 스코프(scope)를 방해할 걱정이 없는 포켓 디멘션(Pocket dimensions)입니다.

이제, 여러분 중 일부—특히 JavaScript 초기부터 활동해 온 분들—는 아마 이러한 예시들을 보고 움찔했을 것입니다. ShadowRealm API가 그저 고스(goth) 스타일의 eval일 뿐이라고 생각해도 무방하며, 그것이 완전히 틀린 말도 아닙니다. ShadowRealm의 컨텍스트(context)에서 실행된다는 점을 제외하면, 지금까지 보신 것들은 기본적으로 eval에 대한 간접 호출과 다를 바 없습니다. 심지어 동일한 unsafe-eval 콘텐츠 보안 정책 (Content Security Policy) 규칙의 적용을 받습니다.

하지만 여러분의 워크플로(workflows)에 대해 너무 걱정하지 마세요. 이 예시들은 단지 _설명용(illustrative)_일 뿐이며, ShadowRealm을 사용하는 방법이 이것뿐인 것은 아닙니다. 제안된 명세에는 ShadowRealm 객체의 프로토타입(prototype)에 importValue 메서드가 포함되어 있어, 모듈을 동적으로 임포트(import)한 다음 내보내진(exported) 값과 함수를 캡처하여 작업할 수 있습니다:

// spookycode.js
export function greeting() {
 return "Hello from the ShadowRealm!";
...
async function shadowGreeter() {
  // 나는 SHADOWREALM의 어둠의 힘을 소환한다- 에헴. 죄송합니다.
  const shadow = new ShadowRealm();
...

그림자가 아직 드리우지는 않았습니다

이 시점에서 여러분은 제안된 ShadowRealms API의 _전체(entirety)_를 확인하셨다고 말씀드릴 수 있어 기쁩니다. 이 제안에는 여러분이 여기서 본 두 가지 메서드인 evaluateimportValue만이 포함되어 있습니다. 이 두 가지 모두 코드를 호스트 렐름(host realm) 스레드의 컨텍스트에서 _실행(executing)_하면서도, ShadowRealm 인스턴스의 컨텍스트 내에서 코드를 평가(evaluating)함으로써 ~추방(banishing)~하는 수단입니다.

다시 한번 말씀드리지만, 이 중 어느 것도 아직 바로 사용할 수는 없습니다. 제안된 명세는 현재 Stage 2.7 단계인 “원칙적으로 승인되었으며 검증 진행 중(approved in principle and undergoing validation)” 상태입니다. 즉, 브라우저에서의 테스트 및 시도적 구현(trial implementations)을 통한 피드백의 결과로 변경될 가능성이 있을 뿐, 변경되지 않을 수도 있음을 의미합니다. 이 글을 읽고 계신 여러분은 한 수 앞서 나가고 있는 것입니다. 이 제안이 Stage 3에 도달하고 브라우저에서 구현체가 보이기 시작할 때, 여러분은 직접 시도해 볼 준비가 되어 있을 것입니다. 아니, 준비된 정도가 아니라 — ShadowRealm의 경이로운 힘이 웹에 풀리는 그 시점에, 여러분은 그 어둡고 두려운 마법(majjycks)을 부릴 준비를 마친 상태로 서 있게 될 것입니다! 우리 코드가 서 있는 바로 그 영역이 진동하리니, 마치— 알았어요, 알았다고요, 죄송합니다. 봐요, 어쩔 수가 없잖아요! 내 말은, 세상에, 이름부터가 "ShadowRealm"이라니까요.

AI 자동 생성 콘텐츠

본 콘텐츠는 CSS-Tricks의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0