Skip to content

클로저 컴파일러의 역사, 그리고 타입스크립트가 승리한 이유

원문: https://effectivetypescript.com/2023/09/27/closure-compiler/

Closure Tools Logo 이제 6개월만 지나면 지메일이 창립 20주년을 맞이하게 된다는 사실이 나이가 들었음을 느끼게 합니다. 그 당시에 웹사이트를 활발하게 개발하지 않았다면 이 서비스가 얼마나 혁신적이었는지 가늠하기 어려울 것입니다. 이 시기 자바스크립트는 전 세계적으로 저평가를 받았던 시기였습니다. 자바스크립트를 사용하여 정교한 웹 앱을 구축할 수 있다는 생각 자체가 놀라웠습니다. 하지만 자바스크립트는 분명히 효과가 있었고, 단일 페이지 웹 앱(SPA)의 시대를 열었습니다.

이 애플리케이션 뒤에는 구글이 대규모 자바스크립트 애플리케이션을 만들기 위해 개발한 새로운 도구, 즉 클로저(Closure) 도구가 있었습니다(클로저는 'j'가 붙은 클로저가 아니라 's'가 붙은 클로저입니다). 여기에는 타입 검사를 수행하는 자바스크립트 소스-투-소스 컴파일러인 클로저 컴파일러(CC)가 포함되었습니다. 익숙한 이름인가요?

지난 20년 동안 구글에서 프런트엔드 관련 일을 해보지 않았다면 클로저 컴파일러를 접해보지 못했을 가능성이 높습니다. 클로저 컴파일러는 타입스크립트와 비슷한 틈새 시장을 점유하고 있었지만, 타입스크립트가 절대적으로 확실하게 승리했습니다.

그럼에도 불구하고, 몇 가지 이유로 클로저 컴파일러를 다시 살펴보는 것은 흥미롭습니다.

  1. 타입스크립트와 다른 높은 수준의 설계 결정을 내린 시스템을 살펴봄으로써 타입스크립트의 설계에 대해 더 깊이 이해할 수 있습니다.
  2. 우리가 원한다고 생각하지도 못했던 타입스크립트의 누락된 기능을 보여줍니다.
  3. 자바스크립트 역사에서 흥미로운 사례 연구입니다.

다시 말해, 클로저 컴파일러의 이야기는 우리에게 몇 가지 관점을 제공합니다. 타입스크립트가 너무 보편화되어 자바스크립트에 타입 검사기를 추가하는 다른 방법을 상상하기 어려울 때가 있습니다. 돌이켜보면 클로저 컴파일러는 설계 영역이 생각보다 넓었다는 것을 보여줍니다.

저는 2012년부터 2014년까지 구글에서 클로저 스타일 자바스크립트를 가장 많이 썼습니다. 이 글은 당시 존재했던 클로저를 기준으로 작성했습니다. 그 이후로 어떻게 발전했는지는 잘 모르겠습니다.

클로저 컴파일러란 무엇인가요?

타입스크립트의 모토는 "타입스크립트는 자바스크립트의 상위 집합(superset)이다"입니다. 반면에 클로저 코드는 자바스크립트입니다. 언어에 새로운 구문을 추가하지 않습니다.

타입스크립트를 --checkJs 옵션과 함께 사용해 본 적이 있다면 비슷한 개념입니다. 새로운 구문을 통해 자바스크립트에 타입을 추가하는 대신 JSDoc 스타일의 주석을 통해 타입을 추가합니다.

이 타입스크립트를 비교해 보세요.

ts
function max(a: number, b: number): number {
  return a > b ? a : b;
}

이에 상응하는 클로저화된 자바스크립트 코드입니다.

js
/**
 * @param {number} a
 * @param {number} b 
 * @return {number} 
 */
function max(a, b) {
  return a > b ? a : b;
}

max를 잘못 호출하면 오류가 발생합니다.

> google-closure-compiler "--warning_level" "VERBOSE" "max.js"

max.js:12:16: WARNING - [JSC_TYPE_MISMATCH] actual parameter 1 of max does not match formal parameter
found   : string
required: number  
12| console.log(max('foo', 'bar'));
                    ^^^^^

max.js:12:23: WARNING - [JSC_TYPE_MISMATCH] actual parameter 2 of max does not match formal parameter
found   : string
required: number  
12| console.log(max('foo', 'bar'));
                    ^^^^^

0 error(s), 2 warning(s), 100.0% typed
function max(a,b){return a>b?a:b}console.log(max("foo","bar"));

이것은 어떤 면에서는 tsc가 하는 일과 비슷하지만 다른 면에서는 다릅니다. tsc와 마찬가지로 코드의 타입 오류를 보고합니다. 그리고 tsc와 마찬가지로 자바스크립트 코드(마지막 줄)를 출력합니다. 높은 수준에서 보면 타입 검사와 자바스크립트 코드 출력도 타입스크립트가 하는 두 가지 일입니다.

몇 가지 흥미로운 차이점도 있습니다. 클로저 컴파일러는 코드가 "100.0% 타입이 지정됨"이라고 보고합니다. 타입스크립트 용어를 사용한다면, 이것은 얼마나 많은 any 타입이 있는지를 나타내는 척도입니다. (이펙티브 타입스크립트에서는 이 정보를 얻기 위해 타입 커버리지 도구를 사용하는 방법에 대해 '44번 아이템: 타입 커버리지를 추적하여 타입 안정성 유지하기'에서 설명합니다.)

또 다른 흥미로운 차이점은 출력이 경량화(minify)된다는 것입니다. 이는 클로저 컴파일러의 기본 설계 목표인 가능한 한 가장 작은 자바스크립트를 생성하는 데 도움이 됩니다.

설계 목표로서의 경량화

2004년에 지메일이 출시되었을 때 네트워크 속도는 지금보다 훨씬 더 느렸습니다. 지메일 팀은 런타임 자바스크립트 성능이 다운로드 시간과 거의 관련이 없다는 사실을 발견했습니다(업데이트: 이것은 옳지 않습니다, 아래 참조). 페이지 로딩 속도를 높이려면 자바스크립트 번들을 더 작게 만들어야 했습니다. 이것이 바로 클로저 컴파일러와 "고급 최적화" 모드의 핵심 목표입니다.

이것이 어떻게 작동하는지 알아보기 위해 네트워크에서 데이터를 가져와 처리하는 코드를 살펴봅시다.

다음은 타입을 정의하고 함수를 선언하는 "externs" 파일(클로저 컴파일러에서 타입 선언 파일과 동일)입니다.

js
// api-externs.js
/**
 * @typedef {{
 *   foo: string,
 *   bar: number,
 * }}
 */
let APIResponse;

/** @return {APIResponse} */
function fetchData() {}

여기서 주목해야 할 몇 가지 흥미로운 점이 있습니다.

  • 타입은 JSDoc 주석의 @typedef를 통해 구현됩니다. 런타임에 APIResponse 심벌이 존재하지만 특별히 유용하지는 않습니다. 클로저 컴파일러가 자바스크립트라고 해서 자바스크립트가 항상 합리적이라는 의미는 아닙니다.
  • fetchData 선언에는 빈 구현이 포함되어 있습니다. 타입스크립트에서는 여기에 declare function을 사용하지만, 이는 자바스크립트 구문이 아닙니다. 따라서 클로저 컴파일러는 빈 함수 본문을 사용합니다.

다음은 데이터를 가져와서 처리하는 코드입니다.

js
// api.js
/** 
 * @typedef {{ 
 *   longPropertyName: string,
 *   anotherLongName: number
 * }}
 */
let ProcessedData;

/**
 * @param {APIResponse} data
 * @return {ProcessedData}
 */
function processData(data) {
  return {
    longPropertyName: data.foo,
    anotherLongName: data.bar,
  };
}

const apiData = fetchData();
const processedData = processData(apiData);
console.log(processedData.longPropertyName, processedData.anotherLongName);

이 코드는 자바스크립트이기 때문에 <script> 태그를 통해 직접 실행할 수 있습니다(Node.js가 나오기 전에 클로저 컴파일러가 있었습니다). 빌드 단계가 필요하지 않으며 반복 주기가 매우 짧아집니다.

이 코드를 컴파일하면 어떤 일이 일어나는지 살펴봅시다.

> google-closure-compiler "--warning_level" "VERBOSE" "--externs" "api-externs.js" "api.js"

let ProcessedData;function processData(a){return{longPropertyName:a.foo,anotherLongName:a.bar}}const apiData=fetchData(),processedData=processData(apiData);console.log(processedData.longPropertyName,processedData.anotherLongName);

경량화를 해제했을 때의 모습은 다음과 같습니다.

js
let ProcessedData;

function processData(a) {    
  return {        
    longPropertyName: a.foo,
    anotherLongName: a.bar
    };
  }

const apiData = fetchData(), processedData = processData(apiData);

console.log(processedData.longPropertyName, processedData.anotherLongName);

타입스크립트와 마찬가지로, 여기서 컴파일은 대부분 타입 정보(이 경우 JSDoc 주석)를 제거하는 것으로 구성됩니다.

이제 "고급 최적화"를 켜면 어떻게 되는지 살펴봅시다.

> google-closure-compiler "--compilation_level" "ADVANCED" "--warning_level" "VERBOSE" "--externs" "api-externs.js" "api.js"

var a,b=fetchData();a={h:b.foo,g:b.bar};console.log(a.h,a.g);

출력은 훨씬 더 짧아집니다. 다음은 경량화되지 않은 상태의 모습입니다.

js
var a, b = fetchData();

a = {    
  h: b.foo,    
  g: b.bar
};

console.log(a.h, a.g);

이 코드는 원래 코드를 급격하게 변형했습니다. 클로저 컴파일러는 변수 이름을 난독화(apiDatab로, processedDataa로)했을 뿐 아니라 ProcessedData의 프로퍼티 이름을 난독화(longPropertyNameh, anotherLongNameg)하고 processData 호출을 인라인하여 해당 함수를 완전히 제거했습니다.

결과는 극적이었습니다. 단순 최적화를 통해 축소된 코드는 231바이트인 반면, 고급 최적화를 통해 축소된 코드는 62바이트에 불과합니다!

클로저 컴파일러가 일부 심벌, 즉 fetchData 함수와 foobar 프로퍼티 이름을 보존한 것을 주목하세요. "externs" 파일에 있는 심벌은 외부로 공개되므로 변경할 수 없지만, 다른 곳에 있는 심벌은 내부 클로저 컴파일러가 적절하다고 판단하는 대로 난독화하거나 인라인할 수 있다는 규칙이 있습니다.

이는 타입스크립트가 하는 일과는 근본적으로 다릅니다. 타입스크립트는 자바스크립트를 생성할 때 심벌의 이름을 바꾸지 않으며 코드를 경량화하려고 시도하지도 않습니다. 생성된 자바스크립트를 경량화 프로그램을 통해 실행하더라도 이렇게 급진적인 작업을 수행하지는 않습니다. 경량화 프로그램은 어떤 심벌이나 프로퍼티 이름이 외부 API의 일부인지 알기 어렵습니다(또는 불가능합니다). 따라서 프로퍼티 이름을 난독화하는 것은 일반적으로 안전하지 않습니다. 타입스크립트를 사용하면 231바이트의 "단순 최적화" 출력보다 작은 결과를 얻을 가능성은 거의 없습니다.

이러한 결과는 일반적으로 gzip 압축 후에도 잘 유지되며, 대규모 프로젝트에서도 마찬가지입니다. 저는 2013년에 자바스크립트 라이브러리를 클로저로 포팅한 결과, uglifyjs 대비 번들 크기를 40% 줄였습니다.

정말 대단한 일이죠! 그렇다면 왜 클로저 컴파일러가 널리 사용되지 않았을까요?

설계 목표로서 경량화의 문제

'externs' 파일은 올바른 경량화를 위해 매우 중요했습니다. 이 파일이 없었다면 클로저 컴파일러는 fetchData 함수 이름과 foobar 프로퍼티도 손상시켰을 것이고, 이로 인해 런타임 오류가 발생했을 것입니다. 'externs' 파일에서 심벌을 생략하면 추적하기 매우 어려울 수 있는 잘못된 런타임 동작이 발생할 수 있습니다. 다시 말해, 이는 개발자 경험(DX)에 정말로 좋지 않았습니다.

클로저 컴파일러는 이를 처리하기 위해 몇 가지 언어 외적인 규칙을 도입했습니다. 예를 들어, 자바스크립트(및 타입스크립트)에서는 점 표기법과 대괄호를 사용하여 객체의 프로퍼티에 액세스 하는 것을 구분하지 않습니다.

js
const a = obj.property;
const b = obj['property'];
console.log(a, b);  // 정확하게 동일합니다.

클로저 컴파일러에서는 그렇지 않습니다. 따옴표로 묶인 프로퍼티 액세스는 보존되는 반면 점으로 묶인 프로퍼티는 경량화 될 수 있다는 것이 규칙입니다. 다음은 고급 최적화를 통해 해당 코드가 경량화 프로그램을 거친 결과입니다.

js
console.log(obj.g,obj.property);

프로퍼티 이름이 어떻게 달라졌는지 주목하세요. 다시 말해, 클로저화된 자바스크립트는 자바스크립트일 뿐이기도 하지만 그렇지 않은 경우도 있습니다.

고급 최적화에는 또 다른 큰 문제가 있습니다. 프로퍼티 이름을 일관되게 경량화하려면 클로저 컴파일러가 해당 이름을 사용할 수 있는 모든 소스 코드에 액세스 할 수 있어야 한다는 것입니다. 이것이 최대한 효과적이려면 코드가 임포트 하는 모든 코드와 마찬가지로 임포트 하는 모든 코드도 클로저 컴파일러를 염두에 두고 작성해야 합니다.

2023년 npm의 맥락에서 이것은 불가능합니다. 대부분의 프로젝트에서 코드 줄의 최소 90% 이상이 서드파티 코드입니다. 이러한 스타일의 경량화가 효과적이려면 모든 코드가 클로저 컴파일러를 염두에 두고 작성되고 클로저 컴파일러에 의해 하나의 단위로 컴파일되어야 합니다.

반면에 2004년이나 2012년, 심지어 오늘날의 구글에서는 이 비율이 매우 현실적입니다. 대기업에서는 자사 코드와 서드파티 코드의 비율이 뒤바뀌는 경향이 있습니다. 서드파티 코드를 사용하면 법적 및 보안 문제가 발생할 뿐만 아니라 제어권을 잃게 되므로 더 많은 어려움이 따릅니다. 타입스크립트의 제로 런타임 종속성이 그 좋은 예입니다.

구글의 모든 자바스크립트는 클로저 컴파일러를 염두에 두고 작성되었으며, 대부분의 자바스크립트는 퍼스트 파티입니다. 따라서 고급 최적화가 훌륭하게 작동합니다. 하지만 나머지 자바스크립트 세계는 그런 식으로 작동하지 않습니다. 클로저 컴파일러를 염두에 두고 작성되지 않은 리액트나 Lodash와 같은 종속성을 가져오는 즉시 그 가치를 잃기 시작합니다.

이와 비교해 타입스크립트는 기존 라이브러리의 타입만 알면 됩니다. 이것이 타입 검사에 필요한 전부입니다. DefinitelyTyped 프로젝트는 거대한 작업이었지만, 일반적으로 거의 모든 자바스크립트 라이브러리에 대한 타입스크립트 타입을 얻을 수 있습니다. (클로저 컴파일러에 널리 사용되는 자바스크립트 라이브러리에 대한 타입 검사를 위한, 훨씬 작지만 유사한 externs 세트가 있습니다).

좀 더 직접적으로 말하자면, 고급 최적화를 위해서는 컴파일러가 라이브러리의 타입뿐만 아니라 라이브러리의 구현까지 이해해야 하는데, 자바스크립트 생태계의 엄청난 다양성을 고려할 때 이는 실현 불가능한 일입니다.

타이밍이 모든 것입니다

Cover of Closure: The Definitive Guide (2010)

구글은 2004년에 클로저를 개발했지만 2009년 말까지 오픈소스로 공개하지 않았습니다. O'REILLY의 책인 클로저: 최종 가이드는 2010년에 나왔습니다.

돌이켜보면 이 시기는 정말 끔찍했습니다. 2010년에 자바스크립트는 이제 막 최대 침체기에 접어들고 있었습니다. 자바스크립트: 좋은 부분은 2008년에 나왔고, ES5는 2009년에 새로운 "strict" 모드에서 많은 권장 사항을 체계화했습니다. 2009년에는 Node.js가 처음 출시되었고, 2010년에는 npm이 그 뒤를 이어 오늘날 우리가 알고 있는 자바스크립트 패키지의 생태계를 만들었습니다. npm은 2011년부터 browserify가 클라이언트 측 코드에 적용할 수 있게 되면서 훨씬 더 강력하고 유용하게 성장했습니다.

그리고 마침내 2010년에 CoffeeScript가 출시되었습니다. 이는 "개선된" 자바스크립트를 일반 자바스크립트로 컴파일하고 빌드 단계를 갖는다는 개념을 표준화했습니다. 이 모든 것이 자바스크립트의 방향에 영향을 미쳤으며, ES2015는 커피스크립트의 장점 중 일부를 언어 자체에 도입했습니다.

클로저 컴파일러는 자바스크립트를 피해야 하는 '나쁜' 언어로 인식되던 시절에 개발되었습니다. 클로저 컴파일러 자체는 자바로 구현되어 있어 전체 자바스크립트 툴체인에 통합하기가 어려웠습니다. 그리고 자바스크립트에 누락된 부분을 추가하려고 시도했습니다. 새로운 구문을 추가할 수 없었기 때문에 특별한 함수를 사용했습니다. 모듈 시스템을 제공하는 goog.providegoog.require, 클래스 계층구조를 생성하는 과정을 부드럽게 해주는 goog.inherits 등이 그것입니다. 이들은 런타임에 무언가를 수행하는 실제 자바스크립트 함수였습니다. 기억이 확실하지 않지만 goog.require<script> 태그를 삽입할 수도 있었습니다!

여기에는 몇 가지 문제가 있었습니다. 첫째, 모든 goog 함수가 주로 구글을 위해 만들어진 도구라는 생각을 강화한다는 점이었습니다. 자바에서는 패키지에 회사 이름을 넣는 것이 일반적이기 때문에 클로저 개발자에게는 자연스러운 일이었을 것입니다. 하지만 자바스크립트에서는 그렇지 않습니다. facebook/react가 아니라 그냥 reactimport합니다.

둘째, 자바스크립트 자체에 모듈 시스템과 class 키워드가 생겼을 때 어색했습니다. 타입스크립트도 초창기에는 이러한 문제 중 일부에 직면했습니다. 자체 모듈 시스템과 클래스 시스템을 가지고 있었지만, 생태계 일관성을 위해 네이티브 솔루션을 선호하여 이를 폐기했습니다. 타입스크립트는 이제 자바스크립트를 자바스크립트답게 만들고 타입 시스템에서만 혁신을 이루었습니다.

이러한 전환은 타입스크립트 역사 초기에 일어났지만 클로저 컴파일러의 경우에는 늦게 일어났습니다. 아마도 적응이 더 어려웠을 것입니다.

타입스크립트가 승리한 이유

타입스크립트는 더 나은 시기에 등장했고 지난 10년 동안 자바스크립트와 그 생태계의 변화에 적응할 수 있었습니다. 자체 호스팅(tsc는 타입스크립트로 작성됨)되고 npm으로 배포됩니다.

타입스크립트는 또한 개발자 도구에 더 중점을 두어 승리했습니다. 클로저 컴파일러는 오프라인 시스템으로, 사용자가 명령을 실행하면 프로그램이 오류를 검사한 다음 수정하는 과정을 반복합니다. 표준 클로저 언어 서비스는 제가 아는 한 없습니다. 에디터에서 심벌을 검사하여 클로저 컴파일러가 그 타입이 무엇이라고 생각하는지 확인하는 것과 같은 것은 없습니다. 반면에 타입스크립트는 tsc만큼이나 tsserver에 중점을 둡니다. 특히 타입스크립트로 작성되어 2015년에 출시된 비주얼 스튜디오 코드에서는 타입스크립트를 사용하는 것은 즐겁습니다. 클로저가 실수를 지적하기 위해 타입을 사용했다면, 타입스크립트는 생산성을 높이기 위해 타입을 사용합니다. 개발자들이 타입스크립트를 선호하는 것은 당연한 일입니다!

(구글 엔지니어도 예외는 아닙니다. 지난 10년 동안 그들은 타입스크립트를 채택하고 일괄적으로 마이그레이션 했습니다. 한 팀의 경험담인 클로저에서 타입스크립트로 크롬 개발자 도구 포팅을 읽어보세요.)

타입스크립트는 자바스크립트 커뮤니티의 참여를 유도하는 데 더 효과적이었습니다. 타입스크립트는 깃허브에서 공개적으로 개발 및 계획됩니다. 누구의 버그 리포트에서든 응답하고, 마이크로소프트가 아닌 사용자도 중요한 고객으로 대합니다. 반면에 클로저 도구는 구글 내부 도구의 오픈 소스 릴리스였습니다. 구글이 항상 주요 고객이었으며 외부 사용자는 대부분 혼자서 사용했습니다. goog이라는 네임스페이싱은 이를 더욱 강화했습니다.

빌드 단계를 생략할 수 있다는 점에서 "자바스크립트일 뿐"이라는 클로저의 아이디어는 매력적이었습니다. 2023년에도 여전히 일부 타입스크립트 사용자들은 JSDoc 스타일의 타입 어노테이션과 --checkJs를 선호합니다. 하지만 모든 타입에 JSDoc을 사용하는 것은 어색하고 지저분합니다. 사용성은 중요하며 타입스크립트가 더 나은 것은 부인할 수 없는 사실입니다.

마지막으로, "자바스크립트 + 타입"이라는 타입스크립트의 중심 개념은 클로저 툴의 "경량화"와 "그냥 자바스크립트"라는 개념보다 더 잘 유지되었습니다. 2008년에는 번들에서 바이트를 줄이는 것이 대세였지만, 지금은 네트워크 속도가 훨씬 빨라졌고 번들 크기는 여전히 중요하지만 그 당시만큼 중요하지는 않습니다. 클로저는 극단적인 최소화를 달성하기 위해 사용자와 모든 의존성에 획일화된 시스템을 강요했습니다. 우리는 더 많은 유연성을 확보하기 위해 그 목표를 포기했습니다.

여기에는 일반적인 원칙이 있습니다. Michael Feathers이 2009년 블로그 게시물 모든 개발자가 적어도 두 번은 읽어야 할 10가지 논문에서 D.L. Parnas의 1972년 고전 논문 "시스템을 모듈로 분해할 때 사용할 기준에 대해"에 대해 설명한 내용이 생각납니다.

이 논문에서 제가 정말 좋아하는 또 다른 내용은 그가 예로 든 KWIC 시스템에 대한 그의 언급입니다. 그는 훌륭한 프로그래머라면 코드를 작성하는데 1~2주 정도 걸릴 것이라고 언급했습니다. 오늘날에는 거의 시간이 걸리지 않을 것입니다. 향상된 기술과 더 나은 도구에 엄지손가락을 치켜세웠습니다. 우리는 진전을 이루었습니다.

KWIC 시스템은 기본적으로 텍스트 파일을 정렬합니다. 그렇다면 소프트웨어 개발자로서 우리의 진보를 칭찬하는 것이 맞을까요? 오늘은 한 줄로 설명하겠습니다.

js
console.log(  
  fs.readFileSync('input.txt')  
  .split('\n')  
  .toSorted((a, b) => a.localeCompare(b))  
  .join('\n')
);

하지만 이것이 어떻게 가능한지 생각해 보세요.

  • 전체 파일이 메모리에 들어맞는다고 가정하고 있는데, 1972년에는 거의 틀림없이 그렇지 않았을 것입니다.
  • 가비지 컬렉션 언어를 사용하고 있는데, 당시에는 드물었을 것입니다.
  • 노드 빌트인 및 npm을 통해 방대한 라이브러리를 손쉽게 이용할 수 있습니다.
  • 훌륭한 텍스트 편집기와 운영 체제가 있습니다.
  • 웹과 스택오버플로우가 있으니 참조 매뉴얼을 참고할 필요가 없습니다!

이 모든 것이 하드웨어의 발전 덕분입니다. 하드웨어 개발자들은 우리에게 여분의 트랜지스터를 제공하고 소프트웨어 개발자들은 더 나은 개발 프로세스를 위해 대부분의 트랜지스터를 직접 가져갑니다. 더 빨라진 네트워크 속도와 클로저 컴파일러도 마찬가지입니다. 우리는 더 유연한 개발 프로세스와 에코시스템의 대가로 대역폭의 일부를 확보했습니다.

결론

초창기에는 타입스크립트에 경량화 기능 추가에 대한 논의가 있었지만, 이제 최적화된 출력은 해당 언어에 대한 명시적인 비목표입니다. 타입 기반 경량화가 멋질 거라고 생각해 본 적이 있다면, 클로저 컴파일러는 매우 흥미로운 예시입니다. 매우 효과적일 수 있지만 생태계에 막대한 비용을 초래하기도 합니다.

독립형 외부 도구로서의 클로저 컴파일러는 거의 죽은 것처럼 보입니다(클로저 플레이그라운드는 상당히 고장 나 있고 "Copyright 2009"라고 적혀 있습니다!). 하지만 구글에서는 여전히 살아 있습니다. 타입스크립트를 채택했기 때문에 클로저 컴파일러가 가장 잘하는 일, 즉 경량화를 사용할 수 있습니다. 이를 위해 구글은 타입스크립트가 클로저화된 자바스크립트를 생성할 수 있는 도구인 tsickle을 만들었습니다. 사실 이 도구는 오픈 소스이지만 외부인이 이해하기는 어렵습니다. 앵귤러에서 사용하는 것 같지만 제가 알 수는 없습니다.

자바스크립트 역사에서 흥미로운 교훈을 얻으셨길 바랍니다! 클로저 컴파일러는 자바스크립트 생태계가 다른 원칙과 다른 장단점을 가지고 선택할 수 있었던 대안적인 경로를 보여줍니다.

이 글에 대한 활발한 토론이 해커 뉴스에 있습니다. 특히 Paul Buchheit(지메일의 창시자!)는 런타임 성능이 클로저 컴파일러의 목표였으며 인라이닝/데드 코드 제거가 이를 달성하기 위한 방법이었다고 지적합니다. 모든 게터에 비용이 수반되는, JIT 이전의 IE6 사고방식으로 돌아가기는 어렵습니다! 그렇다고 해서 이 글의 결론이 바뀌는 것은 아닙니다. 또한 클로저 컴파일러는 구글 웹 툴킷(GWT)이 아닙니다.