고급 컴파일

개요

ADVANCED_OPTIMIZATIONScompilation_level로 Closure Compiler를 사용하면 SIMPLE_OPTIMIZATIONS 또는 WHITESPACE_ONLY로 컴파일하는 것보다 압축률이 더 높습니다. ADVANCED_OPTIMIZATIONS로 컴파일하면 코드를 변환하고 기호 이름을 바꾸는 방식이 더 적극적이므로 추가 압축이 가능합니다. 하지만 이 더 적극적인 접근 방식은 ADVANCED_OPTIMIZATIONS를 사용하여 출력 코드가 입력 코드와 동일한 방식으로 작동하도록 할 때 더 주의해야 함을 의미합니다.

이 튜토리얼에서는 ADVANCED_OPTIMIZATIONS 컴파일 수준이 하는 일과 ADVANCED_OPTIMIZATIONS로 컴파일한 후 코드가 작동하도록 할 수 있는 방법을 설명합니다. 또한 컴파일러에서 처리하는 코드 외부의 코드에 정의된 심볼인 extern의 개념도 소개합니다.

이 튜토리얼을 읽기 전에 Java 기반 컴파일러 애플리케이션과 같은 Closure Compiler 도구 중 하나를 사용하여 JavaScript를 컴파일하는 프로세스를 숙지해야 합니다.

용어 관련 참고사항: --compilation_level 명령줄 플래그는 더 일반적으로 사용되는 약어인 ADVANCEDSIMPLE뿐만 아니라 더 정확한 ADVANCED_OPTIMIZATIONSSIMPLE_OPTIMIZATIONS도 지원합니다. 이 문서에서는 긴 형식을 사용하지만 명령줄에서는 이름을 바꿔서 사용할 수 있습니다.

  1. 더 나은 압축
  2. ADVANCED_OPTIMIZATIONS를 사용 설정하는 방법
  3. ADVANCED_OPTIMIZATIONS 사용 시 주의사항
    1. 유지하려는 코드 삭제
    2. 일관되지 않은 숙박 시설 이름
    3. 코드의 두 부분을 별도로 컴파일
    4. 컴파일된 코드와 컴파일되지 않은 코드 간의 참조가 깨짐

더 나은 압축

기본 컴파일 수준인 SIMPLE_OPTIMIZATIONS를 사용하면 Closure Compiler가 지역 변수의 이름을 바꿔 JavaScript를 더 작게 만듭니다. 단축할 수 있는 기호는 지역 변수 외에도 있으며 기호 이름을 바꾸는 것 외에도 코드를 축소하는 방법이 있습니다. ADVANCED_OPTIMIZATIONS를 사용한 컴파일은 다양한 코드 축소 가능성을 활용합니다.

다음 코드의 SIMPLE_OPTIMIZATIONSADVANCED_OPTIMIZATIONS 출력을 비교합니다.

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

SIMPLE_OPTIMIZATIONS로 컴파일하면 코드가 다음과 같이 단축됩니다.

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

ADVANCED_OPTIMIZATIONS로 컴파일하면 코드가 다음과 같이 완전히 단축됩니다.

alert("Flowers");

두 스크립트 모두 "Flowers"라는 알림을 생성하지만 두 번째 스크립트가 훨씬 작습니다.

ADVANCED_OPTIMIZATIONS 수준은 다음과 같은 여러 방식으로 변수 이름을 단순하게 단축하는 것 이상입니다.

  • 더 적극적인 이름 바꾸기:

    SIMPLE_OPTIMIZATIONS로 컴파일하면 스크립트에서 함수에 로컬인 유일한 변수이므로 displayNoteTitle()unusedFunction() 함수의 note 매개변수 이름만 바뀝니다. ADVANCED_OPTIMIZATIONS는 전역 변수 flowerNote의 이름도 바꿉니다.

  • 사용하지 않는 코드 삭제:

    ADVANCED_OPTIMIZATIONS로 컴파일하면 코드에서 호출되지 않으므로 함수 unusedFunction()가 완전히 삭제됩니다.

  • 함수 인라인:

    ADVANCED_OPTIMIZATIONS로 컴파일하면 displayNoteTitle() 호출이 함수의 본문을 구성하는 단일 alert()로 대체됩니다. 함수 호출을 함수의 본문으로 대체하는 것을 '인라인'이라고 합니다. 함수가 더 길거나 복잡한 경우 인라인하면 코드의 동작이 변경될 수 있지만 Closure Compiler는 이 경우 인라인이 안전하고 공간을 절약한다고 판단합니다. ADVANCED_OPTIMIZATIONS를 사용한 컴파일은 안전하게 실행할 수 있다고 판단되면 상수와 일부 변수도 인라인합니다.

이 목록은 ADVANCED_OPTIMIZATIONS 컴파일이 실행할 수 있는 크기 축소 변환의 샘플일 뿐입니다.

ADVANCED_OPTIMIZATIONS를 사용 설정하는 방법

Closure Compiler 애플리케이션에 ADVANCED_OPTIMIZATIONS를 사용 설정하려면 다음 명령어와 같이 명령줄 플래그 --compilation_level ADVANCED_OPTIMIZATIONS를 포함하세요.

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

ADVANCED_OPTIMIZATIONS 사용 시 주의사항

아래에는 ADVANCED_OPTIMIZATIONS의 일반적인 의도하지 않은 효과와 이를 방지하기 위해 취할 수 있는 단계가 나열되어 있습니다.

유지하려는 코드 삭제

아래 함수만 ADVANCED_OPTIMIZATIONS로 컴파일하면 Closure Compiler에서 빈 출력을 생성합니다.

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

컴파일러에 전달하는 JavaScript에서 함수가 호출되지 않으므로 Closure Compiler는 이 코드가 필요하지 않다고 가정합니다.

대부분의 경우 이 동작이 원하는 동작입니다. 예를 들어 코드를 대규모 라이브러리와 함께 컴파일하는 경우 Closure Compiler는 해당 라이브러리에서 실제로 사용하는 함수를 확인하고 사용하지 않는 함수를 삭제할 수 있습니다.

하지만 Closure Compiler가 유지하려는 함수를 삭제하는 경우 이를 방지하는 방법에는 두 가지가 있습니다.

  • 함수 호출을 Closure Compiler에서 처리하는 코드로 이동합니다.
  • 노출하려는 함수의 extern을 포함합니다.

다음 섹션에서는 각 옵션을 자세히 설명합니다.

해결 방법: Closure Compiler에서 처리하는 코드로 함수 호출 이동

Closure Compiler로 코드의 일부만 컴파일하면 원치 않는 코드 삭제가 발생할 수 있습니다. 예를 들어 함수 정의만 포함하는 라이브러리 파일과 라이브러리를 포함하고 이러한 함수를 호출하는 코드를 포함하는 HTML 파일이 있을 수 있습니다. 이 경우 ADVANCED_OPTIMIZATIONS로 라이브러리 파일을 컴파일하면 Closure Compiler가 모든 라이브러리 함수를 삭제합니다.

이 문제의 가장 간단한 해결책은 함수를 호출하는 프로그램 부분과 함께 함수를 컴파일하는 것입니다. 예를 들어 Closure Compiler는 다음 프로그램을 컴파일할 때 displayNoteTitle()을 삭제하지 않습니다.

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

이 경우 displayNoteTitle() 함수는 삭제되지 않습니다. Closure Compiler에서 이 함수가 호출되는 것을 확인하기 때문입니다.

즉, Closure Compiler에 전달하는 코드에 프로그램의 진입점을 포함하면 원치 않는 코드 삭제를 방지할 수 있습니다. 프로그램의 진입점은 프로그램이 실행되기 시작하는 코드의 위치입니다. 예를 들어 이전 섹션의 꽃 메모 프로그램에서 마지막 세 줄은 브라우저에 JavaScript가 로드되는 즉시 실행됩니다. 이 프로그램의 진입점입니다. 유지해야 하는 코드를 확인하기 위해 Closure Compiler는 이 진입점에서 시작하여 프로그램의 제어 흐름을 앞으로 추적합니다.

해결 방법: 노출하려는 함수의 Extern 포함

이 솔루션에 대한 자세한 내용은 아래extern 및 export 페이지를 참고하세요.

일관성 없는 속성 이름

Closure Compiler 컴파일은 사용하는 컴파일 수준과 관계없이 코드의 문자열 리터럴을 변경하지 않습니다. 즉, ADVANCED_OPTIMIZATIONS를 사용한 컴파일은 코드가 문자열로 속성에 액세스하는지에 따라 속성을 다르게 처리합니다. 속성에 대한 문자열 참조를 점 구문 참조와 혼합하면 Closure Compiler가 해당 속성에 대한 일부 참조의 이름을 바꾸지만 다른 참조는 바꾸지 않습니다. 따라서 코드가 올바르게 실행되지 않을 수 있습니다.

예를 들어 다음 코드를 살펴보겠습니다.

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

이 소스 코드의 마지막 두 문은 정확히 동일한 작업을 실행합니다. 하지만 ADVANCED_OPTIMIZATIONS로 코드를 압축하면 다음과 같은 결과가 나옵니다.

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

압축된 코드의 마지막 문장에서 오류가 발생합니다. myTitle 속성에 대한 직접 참조의 이름이 a로 변경되었지만 displayNoteTitle 함수 내에서 myTitle에 대한 인용된 참조의 이름은 변경되지 않았습니다. 따라서 마지막 문은 더 이상 존재하지 않는 myTitle 속성을 참조합니다.

해결 방법: 속성 이름 일관성 유지

이 솔루션은 매우 간단합니다. 특정 유형이나 객체의 경우 점 구문 또는 따옴표로 묶인 문자열만 사용하세요. 특히 동일한 속성을 참조할 때는 구문을 혼합하지 마세요.

또한 가능한 경우 점 구문을 사용하는 것이 좋습니다. 점 구문은 더 나은 검사와 최적화를 지원하기 때문입니다. 이름이 디코딩된 JSON과 같은 외부 소스에서 오는 경우와 같이 Closure Compiler에서 이름 바꾸기를 하지 않으려는 경우에만 따옴표로 묶인 문자열 속성 액세스를 사용하세요.

코드의 두 부분을 별도로 컴파일

애플리케이션을 여러 코드 청크로 분할하는 경우 청크를 별도로 컴파일하는 것이 좋습니다. 하지만 두 코드 청크가 상호작용하는 경우 어려움이 발생할 수 있습니다. 성공하더라도 두 Closure Compiler 실행의 출력은 호환되지 않습니다.

예를 들어 애플리케이션이 데이터를 가져오는 부분과 데이터를 표시하는 부분의 두 부분으로 나뉜다고 가정해 보겠습니다.

다음은 데이터를 가져오는 코드입니다.

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

다음은 데이터를 표시하는 코드입니다.

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

이 두 코드 청크를 별도로 컴파일하려고 하면 여러 문제가 발생합니다. 먼저 Closure Compiler는 보관할 코드 삭제에 설명된 이유로 getData() 함수를 삭제합니다. 두 번째로, Closure Compiler는 데이터를 표시하는 코드를 처리할 때 심각한 오류를 생성합니다.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

컴파일러는 데이터를 표시하는 코드를 컴파일할 때 getData() 함수에 액세스할 수 없으므로 getData를 정의되지 않은 것으로 취급합니다.

해결 방법: 페이지의 모든 코드를 함께 컴파일

올바른 컴파일을 위해 단일 컴파일 실행에서 페이지의 모든 코드를 함께 컴파일하세요. Closure Compiler는 여러 JavaScript 파일과 JavaScript 문자열을 입력으로 허용하므로 단일 컴파일 요청에서 라이브러리 코드와 기타 코드를 함께 전달할 수 있습니다.

참고: 컴파일된 코드와 컴파일되지 않은 코드를 혼합해야 하는 경우에는 이 방법을 사용할 수 없습니다. 이 상황을 처리하는 방법에 관한 도움말은 컴파일된 코드와 컴파일되지 않은 코드 간의 참조 깨짐을 참고하세요.

컴파일된 코드와 컴파일되지 않은 코드 간의 참조가 깨짐

ADVANCED_OPTIMIZATIONS에서 심볼 이름을 바꾸면 Closure Compiler에서 처리한 코드와 다른 코드 간의 통신이 중단됩니다. 컴파일은 소스 코드에 정의된 함수의 이름을 바꿉니다. 함수를 호출하는 외부 코드는 컴파일 후에도 이전 함수 이름을 참조하므로 작동하지 않습니다. 마찬가지로 컴파일된 코드에서 외부적으로 정의된 기호에 대한 참조는 Closure Compiler에 의해 변경될 수 있습니다.

'컴파일되지 않은 코드'에는 eval() 함수에 문자열로 전달된 코드가 포함됩니다. Closure Compiler는 코드의 문자열 리터럴을 변경하지 않으므로 Closure Compiler는 eval() 문에 전달된 문자열을 변경하지 않습니다.

컴파일된 항목에서 외부로의 통신을 유지하는 것과 외부에서 컴파일된 항목으로의 통신을 유지하는 것은 관련이 있지만 서로 다른 문제라는 점에 유의하세요. 이러한 별도의 문제에는 공통 솔루션이 있지만 각 측면에 미묘한 차이가 있습니다. Closure Compiler를 최대한 활용하려면 어떤 사례에 해당하는지 이해하는 것이 중요합니다.

계속하기 전에 extern 및 export를 숙지하는 것이 좋습니다.

컴파일된 코드에서 외부 코드로 호출하는 솔루션: externs로 컴파일

다른 스크립트에서 페이지에 제공한 코드를 사용하는 경우 Closure Compiler가 해당 외부 라이브러리에 정의된 기호에 대한 참조 이름을 바꾸지 않는지 확인해야 합니다. 이렇게 하려면 외부 라이브러리의 extern을 포함하는 파일을 컴파일에 포함하세요. 이렇게 하면 Closure Compiler에 제어할 수 없으므로 변경할 수 없는 이름이 표시됩니다. 코드에서 외부 파일에 사용된 것과 동일한 이름을 사용해야 합니다.

이러한 API의 일반적인 예로는 OpenSocial APIGoogle 지도 API가 있습니다. 예를 들어 코드에서 적절한 externs 없이 OpenSocial 함수 opensocial.newDataRequest()를 호출하는 경우 Closure Compiler는 이 호출을 a.b()로 변환합니다.

외부 코드에서 컴파일된 코드로 호출하기 솔루션: extern 구현

라이브러리로 재사용하는 JavaScript 코드가 있는 경우 컴파일되지 않은 코드가 라이브러리의 함수를 호출할 수 있도록 하면서 라이브러리만 축소하기 위해 Closure Compiler를 사용할 수 있습니다.

이 경우 해결책은 라이브러리의 공개 API를 정의하는 externs 집합을 구현하는 것입니다. 코드는 이러한 extern에서 선언된 기호의 정의를 제공합니다. 이는 외부인이 언급하는 클래스나 함수를 의미합니다. 클래스가 externs에 선언된 인터페이스를 구현해야 할 수도 있습니다.

이러한 extern은 자신뿐만 아니라 다른 사람에게도 유용합니다. 라이브러리의 소비자는 코드를 컴파일하는 경우 이를 포함해야 합니다. 라이브러리가 소비자의 관점에서 외부 스크립트를 나타내기 때문입니다. extern은 개발자와 소비자 간의 계약으로 생각하면 됩니다. 양쪽 모두 사본이 필요합니다.

이를 위해 코드를 컴파일할 때 컴파일에 externs도 포함해야 합니다. extern이 '다른 곳에서 오는' 것으로 생각하는 경우가 많으므로 이상하게 보일 수 있지만, 이름이 바뀌지 않도록 Closure Compiler에 노출하는 심볼을 알려야 합니다.

여기서 한 가지 중요한 주의사항은 외부 심볼을 정의하는 코드에 관한 '중복 정의' 진단이 표시될 수 있다는 것입니다. Closure Compiler는 externs의 모든 심볼이 외부 라이브러리에 의해 제공된다고 가정하며 현재는 의도적으로 정의를 제공하고 있음을 이해할 수 없습니다. 이러한 진단은 억제해도 안전하며, 억제는 API를 실제로 충족하고 있음을 확인하는 것으로 생각할 수 있습니다.

또한 Closure Compiler는 정의가 외부 선언의 유형과 일치하는지 유형 검사를 실행할 수 있습니다. 이렇게 하면 정의가 올바른지 추가로 확인할 수 있습니다.