고급 컴파일

개요

ADVANCED_OPTIMIZATIONScompilation_level와 함께 클로저 컴파일러를 사용하면 SIMPLE_OPTIMIZATIONS 또는 WHITESPACE_ONLY로 컴파일하는 것보다 더 나은 압축률을 제공합니다. ADVANCED_OPTIMIZATIONS로 컴파일하면 코드를 변환하고 기호의 이름을 바꾸는 방식으로 더 적극적으로 압축하여 추가 압축이 이루어집니다. 그러나 이처럼 더 공격적인 접근 방식은 ADVANCED_OPTIMIZATIONS를 사용할 때 출력 코드가 입력 코드와 동일한 방식으로 작동하도록 해야 하므로 더 주의해야 합니다.

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

이 튜토리얼을 읽기 전에 클로저 컴파일러 도구(컴파일러 서비스 UI, 컴파일러 서비스 API 또는 컴파일러 애플리케이션) 중 하나로 자바스크립트를 컴파일하는 프로세스를 숙지해야 합니다.

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

  1. 압축도 훨씬 개선
  2. ADVANCED_OPTIMIZATIONS 사용 설정 방법
  3. ADVANCED_OPTIMIZATIONS 사용 시 주의사항
    1. 보관하고 싶은 코드 삭제
    2. 일관되지 않은 속성 이름
    3. 두 부분의 코드를 별도로 컴파일
    4. 컴파일된 코드와 컴파일되지 않은 코드 간의 손상된 참조

압축 성능 개선

기본 컴파일 수준 SIMPLE_OPTIMIZATIONS에서 클로저 컴파일러를 사용하면 로컬 변수의 이름을 변경하여 자바스크립트를 더 작게 만들 수 있습니다. 하지만 로컬 변수 외에 단축할 수 있는 기호가 있으며 기호 이름 변경 이외의 코드를 축소하는 방법이 있습니다. 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()로 대체됩니다. 이러한 함수 호출을 함수의 본문으로 대체하는 것을 '인라이닝'이라고 합니다. 함수가 더 길거나 더 복잡한 경우 인라인 처리하면 코드 동작이 변경될 수 있지만 클로저 컴파일러는 이 경우 인라인 처리를 사용하는 것이 안전하다고 판단하여 공간을 절약합니다. ADVANCED_OPTIMIZATIONS로 컴파일하면 안전하게 진행할 수 있다고 판단하는 경우 상수 및 일부 변수도 인라인됩니다.

이 목록은 ADVANCED_OPTIMIZATIONS 컴파일로 실행할 수 있는 크기 축소 변환의 샘플에 불과합니다.

ADVANCED_OPTIMIZATIONS 사용 설정 방법

클로저 컴파일러 서비스 UI, 서비스 API, 애플리케이션에는 모두 compilation_levelADVANCED_OPTIMIZATIONS로 설정하는 메서드가 서로 다릅니다.

클로저 컴파일러 서비스 UI에서 ADVANCED_OPTIMIZATIONS를 사용 설정하는 방법

클로저 컴파일러 서비스 UI에 ADVANCED_OPTIMIZATIONS를 사용 설정하려면 '고급' 라디오 버튼을 클릭하세요.

Closure Compiler 서비스 API에서 ADVANCED_OPTIMIZATIONS를 사용 설정하는 방법

Closure Compiler 서비스 API에 ADVANCED_OPTIMIZATIONS를 사용 설정하려면 다음 Python 프로그램에서와 같이 값이 ADVANCED_OPTIMIZATIONScompilation_level라는 요청 매개변수를 포함합니다.

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

클로저 컴파일러 애플리케이션에서 ADVANCED_OPTIMIZATIONS를 사용 설정하는 방법

클로저 컴파일러 애플리케이션에 ADVANCED_OPTIMIZATIONS를 사용 설정하려면 다음 명령어와 같이 명령줄 플래그 --compilation_level ADVANCED_OPTIMIZATIONS를 포함하세요.

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

ADVANCED_OPTIMIZATIONS 사용 시 유의사항

다음은 ADVANCED_OPTIMIZATIONS의 의도하지 않은 일반적인 효과와 이를 방지하기 위해 취할 수 있는 조치입니다.

보관하고 싶은 코드 삭제

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

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

컴파일러에 전달하는 자바스크립트에서 함수가 호출되지 않으므로 클로저 컴파일러는 이 코드가 필요하지 않다고 가정합니다.

대부분의 경우 이 동작은 개발자가 원하는 동작입니다. 예를 들어 대형 라이브러리와 함께 코드를 컴파일하는 경우 클로저 컴파일러는 라이브러리에서 실제로 사용하는 함수를 확인하고 사용하지 않는 함수를 삭제할 수 있습니다.

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

  • 함수 호출을 클로저 컴파일러에서 처리된 코드로 이동합니다.
  • 노출하려는 함수의 extern을 포함합니다.

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

해결 방법: 함수 호출을 클로저 컴파일러에서 처리한 코드로 이동

클로저 컴파일러를 사용하여 코드의 일부만 컴파일하는 경우 원치 않는 코드가 삭제될 수 있습니다. 예를 들어 함수 정의만 포함된 라이브러리 파일, 라이브러리 및 이러한 함수를 호출하는 코드가 포함된 HTML 파일이 있을 수 있습니다. 이 경우 라이브러리 파일을 ADVANCED_OPTIMIZATIONS로 컴파일하면 클로저 컴파일러가 모든 라이브러리 함수를 삭제합니다.

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

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

이 경우 클로저 컴파일러에서 함수가 호출되므로 displayNoteTitle() 함수를 삭제하지 않습니다.

즉, 클로저 컴파일러로 전달하는 코드에 프로그램의 진입점을 포함하여 원치 않는 코드 삭제를 방지할 수 있습니다. 프로그램의 진입점은 코드에서 프로그램이 실행을 시작하는 위치입니다. 예를 들어 이전 섹션의 꽃 노트 프로그램에서 자바스크립트가 브라우저에 로드되는 즉시 마지막 세 줄이 실행됩니다. 이 프로그램의 진입점입니다. 어떤 코드를 유지해야 할지 결정하기 위해 클로저 컴파일러가 이 진입점에서 시작하여 거기서부터 프로그램의 제어 흐름을 추적합니다.

해결 방법: 노출하려는 함수에 대한 Exters 포함

이 솔루션에 대한 자세한 내용은 아래외부 및 내보내기 페이지에 나와 있습니다.

일치하지 않는 속성 이름

클로저 컴파일러 컴파일은 코드의 문자열 리터럴을 변경하지 않으며, 사용하는 컴파일 수준은 중요하지 않습니다. 즉, ADVANCED_OPTIMIZATIONS를 사용하여 컴파일할 경우 코드에서 문자열로 액세스하는지 여부에 따라 속성을 다르게 처리합니다. 점 구문 참조를 사용하여 속성에 대한 문자열 참조를 혼합하면 클로저 컴파일러에서 속성에 대한 일부 참조의 이름을 바꾸고 나머지는 변경하지 않습니다. 따라서 코드가 올바르게 실행되지 않을 수 있습니다.

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

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과 같은 외부 소스에서 이름이 지정된 경우와 같이 클로저 컴파일러의 이름을 바꾸지 않으려는 경우에만 따옴표 붙은 문자열 속성 액세스를 사용하세요.

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

애플리케이션을 여러 코드 청크로 분할하는 경우 청크를 별도로 컴파일해야 할 수 있습니다. 그러나 두 개의 코드 청크가 전혀 상호작용하지 않으면 문제가 발생할 수 있습니다. 성공하더라도 두 클로저 컴파일러 실행의 출력은 호환되지 않습니다.

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

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

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());

이 두 개의 코드를 별도로 컴파일하려고 하면 몇 가지 문제가 발생합니다. 먼저 클로저 컴파일러는 유지하려는 코드 삭제에 설명된 이유로 getData() 함수를 삭제합니다. 둘째, 클로저 컴파일러에서 데이터를 표시하는 코드를 처리할 때 치명적인 오류가 발생합니다.

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

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

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

적절한 컴파일을 보장하려면 단일 컴파일 실행으로 페이지의 모든 코드를 함께 컴파일합니다. 클로저 컴파일러는 여러 자바스크립트 파일과 자바스크립트 문자열을 입력으로 허용할 수 있으므로 라이브러리 코드 및 기타 코드를 단일 컴파일 요청으로 함께 전달할 수 있습니다.

참고: 컴파일된 코드와 컴파일되지 않은 코드를 혼합해야 하는 경우 이 방법이 효과가 없습니다. 이 상황을 처리하기 위한 도움말은 컴파일된 코드와 컴파일되지 않은 코드 간의 손상된 참조를 참고하세요.

컴파일된 코드와 컴파일되지 않은 코드 간의 손상된 참조

ADVANCED_OPTIMIZATIONS의 기호 이름 변경은 클로저 컴파일러에서 처리하는 코드와 다른 코드 간의 통신을 중단합니다. 컴파일은 소스 코드에 정의된 함수의 이름을 변경합니다. 함수를 호출하는 외부 코드는 컴파일 후에도 손상됩니다. 이전 함수 이름을 계속 참조하기 때문입니다. 마찬가지로 컴파일된 코드에 있는 외부에서 정의된 기호 참조는 클로저 컴파일러에 의해 변경될 수 있습니다.

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

이러한 문제는 서로 관련이 있지만 별개의 문제입니다. 컴파일된 컴파일에서 외부 통신으로 유지보수하는 작업과 외부에서 컴파일된 통신을 유지하는 것입니다. 이러한 별도의 문제에는 공통된 해결책이 있지만 각 측면에 미묘한 차이가 있습니다. Closure Compiler를 최대한 활용하려면 현재 상황을 이해하는 것이 중요합니다.

계속하기 전에 외부 확장 및 내보내기를 숙지하는 것이 좋습니다.

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

다른 스크립트에서 페이지에 제공된 코드를 사용하는 경우 클로저 컴파일러가 외부 라이브러리에 정의된 기호로 참조의 이름을 바꾸지 않도록 해야 합니다. 이렇게 하려면 외부 라이브러리의 extern이 포함된 파일을 컴파일에 포함합니다. 이렇게 하면 제어할 수 없는 이름을 Closure Compiler에 알리고 변경할 수 없습니다. 코드는 외부 파일에서 사용하는 이름과 동일한 이름을 사용해야 합니다.

일반적인 예로는 OpenSocial APIGoogle Maps API와 같은 API가 있습니다. 예를 들어 코드에서 적절한 외부 함수 없이 OpenSocial 함수 opensocial.newDataRequest()을 호출하면 클로저 컴파일러에서 이 호출을 a.b()로 변환합니다.

외부 코드에서 컴파일된 코드를 호출하는 솔루션: Externs 구현

라이브러리로 재사용하는 자바스크립트 코드가 있는 경우 클로저 컴파일러를 사용하여 라이브러리만 축소하는 동시에 컴파일되지 않은 코드가 라이브러리의 함수를 호출하도록 허용할 수 있습니다.

이 문제를 해결하려면 라이브러리의 공개 API를 정의하는 extern 집합을 구현하면 됩니다. 코드는 이러한 extern에 선언된 기호의 정의를 제공합니다. 즉, 외부인이 언급하는 클래스나 함수가 이에 해당합니다. 또한 클래스가 extern에 선언된 인터페이스를 구현하도록 할 수도 있습니다.

이러한 확장 기능은 사용자뿐만 아니라 다른 사용자에게도 유용합니다. 라이브러리는 소비자가 컴파일하는 측면에서 외부 스크립트를 나타내므로 코드를 컴파일하는 경우 라이브러리 소비자를 포함해야 합니다. 익스텐션은 개발자와 소비자 간의 계약이라고 생각하세요. 둘 다 사본이 필요합니다.

이를 위해 코드를 컴파일할 때 컴파일에 extern을 포함해야 합니다. extern은 종종 '다른 곳에서 비롯'되는 것으로 생각하기 때문에 비정상적으로 보일 수 있지만 이름이 변경되지 않도록 어떤 기호를 노출하는지 클로저 컴파일러에 알려야 합니다.

여기서 주의해야 할 한 가지 중요한 사항은 외부 기호를 정의하는 코드에 관해 '중복 정의' 진단을 받을 수 있다는 것입니다. 클로저 컴파일러는 외부 라이브러리의 모든 기호가 외부 라이브러리에서 제공되고 있으며 현재 사용자가 의도적으로 정의를 제공한다는 것을 이해할 수 없다고 가정합니다. 이러한 진단은 억제해도 괜찮으며, 억제는 실제로 API를 처리하고 있다는 것을 확인하는 것입니다.

또한 클로저 컴파일러에서 정의가 extern 선언 유형과 일치하는지 확인할 수 있습니다. 이렇게 하면 정의가 올바른지 추가로 확인할 수 있습니다.