V8 런타임으로 스크립트 이전

Rhino 런타임은 2026년 1월 31일 이후에 종료됩니다. Rhino 런타임을 사용하는 기존 스크립트가 있는 경우 스크립트를 V8로 이전해야 합니다.

스크립트에 V8 구문과 기능을 추가하는 데 필요한 유일한 사전 요구사항은 V8 런타임을 사용 설정하는 것입니다. 하지만 V8 런타임에서 스크립트가 실패하거나 예기치 않게 동작할 수 있는 비호환성기타 차이점이 약간 있습니다. V8을 사용하도록 스크립트를 이전할 때는 스크립트 프로젝트에서 이러한 문제를 검색하고 발견된 문제를 수정해야 합니다.

V8 마이그레이션 절차

스크립트를 V8로 이전하려면 다음 절차를 따르세요.

  1. 스크립트에 대해 V8 런타임을 사용 설정합니다. runtimeVersion는 Apps Script 프로젝트의 매니페스트를 사용하여 확인할 수 있습니다.
  2. 다음 비호환성을 주의 깊게 검토하세요. 스크립트를 검사하여 비호환성이 있는지 확인합니다. 하나 이상의 비호환성이 있는 경우 스크립트 코드를 조정하여 문제를 삭제하거나 방지합니다.
  3. 다음 기타 차이점을 주의 깊게 검토하세요. 스크립트를 검토하여 나열된 차이점이 코드 동작에 영향을 미치는지 확인합니다. 스크립트를 조정하여 동작을 수정합니다.
  4. 발견된 비호환성이나 기타 차이점을 수정한 후 V8 구문 및 기타 기능을 사용하도록 코드를 업데이트할 수 있습니다.
  5. 코드 조정을 완료한 후 스크립트가 예상대로 작동하는지 철저히 테스트합니다.
  6. 스크립트가 웹 앱이거나 게시된 부가기능인 경우 V8 조정이 적용된 스크립트의 새 버전을 만들어 배포를 새로 만든 버전으로 지정해야 합니다. 사용자가 V8 버전을 사용할 수 있도록 하려면 이 버전으로 스크립트를 다시 게시해야 합니다.
  7. 스크립트가 라이브러리로 사용되는 경우 스크립트의 새 버전이 지정된 배포를 만듭니다. 이 새 버전을 라이브러리를 사용하는 모든 스크립트와 사용자에게 전달하여 V8 지원 버전으로 업데이트하도록 안내합니다. 이전 Rhino 기반 버전의 라이브러리가 더 이상 활성 상태로 사용되거나 액세스할 수 없는지 확인합니다.
  8. 스크립트 인스턴스가 기존 Rhino 런타임에서 계속 작동하지 않는지 확인합니다. 모든 배포가 V8에 있는 버전과 연결되어 있는지 확인합니다. 오래된 배포를 보관처리합니다. 모든 버전을 검토하고 V8 런타임을 사용하지 않는 버전을 삭제합니다.

비호환성

원래 Rhino 기반 Apps Script 런타임에서는 몇 가지 비표준 ECMAScript 동작이 허용되었습니다. V8은 표준을 준수하므로 이전 후에는 이러한 동작이 지원되지 않습니다. 이러한 문제를 수정하지 않으면 V8 런타임이 사용 설정된 후 오류가 발생하거나 스크립트 동작이 중단됩니다.

다음 섹션에서는 이러한 각 동작과 V8로 마이그레이션하는 동안 스크립트 코드를 수정하기 위해 취해야 하는 단계를 설명합니다.

for each(variable in object) 사용 자제

for each (variable in object) 문은 JavaScript 1.6에 추가되었으며 for...of을 위해 삭제되었습니다.

스크립트를 V8로 이전할 때는 for each (variable in object) 문을 사용하지 마세요.

대신 for (variable in object)를 사용하세요.

// Rhino runtime
var obj = {a: 1, b: 2, c: 3};

// Don't use 'for each' in V8
for each (var value in obj) {
  Logger.log("value = %s", value);
}
      
// V8 runtime
var obj = {a: 1, b: 2, c: 3};

for (var key in obj) {  // OK in V8
  var value = obj[key];
  Logger.log("value = %s", value);
}
      

Date.prototype.getYear() 사용 자제

원래 Rhino 런타임에서 Date.prototype.getYear()은 1900~1999년의 경우 두 자리 연도를 반환하지만 다른 날짜의 경우 네 자리 연도를 반환합니다. 이는 JavaScript 1.2 이하의 동작입니다.

V8 런타임에서 Date.prototype.getYear()은 ECMAScript 표준에 따라 1900을 뺀 연도를 대신 반환합니다.

스크립트를 V8로 이전할 때는 항상 Date.prototype.getFullYear()를 사용하세요. 날짜와 관계없이 4자리 연도를 반환합니다.

예약된 키워드를 이름으로 사용하지 마세요.

ECMAScript에서는 함수 및 변수 이름에 특정 예약어를 사용하는 것을 금지합니다. Rhino 런타임에서는 이러한 단어를 많이 허용했으므로 코드가 이러한 단어를 사용하는 경우 함수나 변수의 이름을 바꿔야 합니다.

스크립트를 V8로 이전할 때는 예약된 키워드 중 하나를 사용하여 변수나 함수 이름을 지정하지 마세요. 키워드 이름을 사용하지 않도록 변수나 함수 이름을 바꿉니다. 이름으로 사용되는 키워드의 일반적인 예는 class, import, export입니다.

const 변수 재할당 방지

원래 Rhino 런타임에서는 const을 사용하여 변수를 선언할 수 있습니다. 이는 기호의 값이 변경되지 않으며 기호에 대한 향후 할당이 무시됨을 의미합니다.

새 V8 런타임에서 const 키워드는 표준을 준수하며 const로 선언된 변수에 할당하면 TypeError: Assignment to constant variable 런타임 오류가 발생합니다.

스크립트를 V8로 이전할 때는 const 변수의 값을 다시 할당하지 마세요.

// Rhino runtime
const x = 1;
x = 2;          // No error
console.log(x); // Outputs 1
      
// V8 runtime
const x = 1;
x = 2;          // Throws TypeError
console.log(x); // Never executed
      

XML 리터럴 및 XML 객체 사용하지 않기

ECMAScript의 이 비표준 확장 프로그램을 사용하면 Apps Script 프로젝트에서 XML 구문을 직접 사용할 수 있습니다.

스크립트를 V8로 이전할 때는 직접 XML 리터럴이나 XML 객체를 사용하지 마세요.

대신 XmlService를 사용하여 XML을 파싱합니다.

// V8 runtime
var incompatibleXml1 = <container><item/></container>;             // Don't use
var incompatibleXml2 = new XML('<container><item/></container>');  // Don't use

var xml3 = XmlService.parse('<container><item/></container>');     // OK
      

__iterator__를 사용하여 맞춤 반복자 함수를 빌드하지 마세요.

JavaScript 1.7에서는 클래스의 프로토타입에 __iterator__ 함수를 선언하여 모든 클래스에 맞춤 반복자를 추가할 수 있는 기능이 추가되었습니다. 이 기능은 개발자의 편의를 위해 Apps Script의 Rhino 런타임에도 추가되었습니다. 하지만 이 기능은 ECMA-262 표준의 일부가 아니었으며 ECMAScript 호환 JavaScript 엔진에서 삭제되었습니다. V8을 사용하는 스크립트는 이 반복자 구성을 사용할 수 없습니다.

스크립트를 V8로 이전할 때는 __iterator__ 함수를 사용하여 맞춤 반복자를 빌드하지 마세요. 대신 ECMAScript 6 반복자를 사용하세요.

다음 배열 구성을 고려해 보세요.

// Create a sample array
var myArray = ['a', 'b', 'c'];
// Add a property to the array
myArray.foo = 'bar';

// The default behavior for an array is to return keys of all properties,
//  including 'foo'.
Logger.log("Normal for...in loop:");
for (var item in myArray) {
  Logger.log(item);            // Logs 0, 1, 2, foo
}

// To only log the array values with `for..in`, a custom iterator can be used.
      

다음 코드 예시에서는 Rhino 런타임에서 반복자를 구성하는 방법과 V8 런타임에서 대체 반복자를 구성하는 방법을 보여줍니다.

// Rhino runtime custom iterator
function ArrayIterator(array) {
  this.array = array;
  this.currentIndex = 0;
}

ArrayIterator.prototype.next = function() {
  if (this.currentIndex
      >= this.array.length) {
    throw StopIteration;
  }
  return "[" + this.currentIndex
    + "]=" + this.array[this.currentIndex++];
};

// Direct myArray to use the custom iterator
myArray.__iterator__ = function() {
  return new ArrayIterator(this);
}


Logger.log("With custom Rhino iterator:");
for (var item in myArray) {
  // Logs [0]=a, [1]=b, [2]=c
  Logger.log(item);
}
      
// V8 runtime (ECMAScript 6) custom iterator
myArray[Symbol.iterator] = function() {
  var currentIndex = 0;
  var array = this;

  return {
    next: function() {
      if (currentIndex < array.length) {
        return {
          value: "[${currentIndex}]="
            + array[currentIndex++],
          done: false};
      } else {
        return {done: true};
      }
    }
  };
}

Logger.log("With V8 custom iterator:");
// Must use for...of since
//   for...in doesn't expect an iterable.
for (var item of myArray) {
  // Logs [0]=a, [1]=b, [2]=c
  Logger.log(item);
}
      

조건부 catch 절 방지

V8 런타임은 표준을 준수하지 않으므로 catch..if 조건부 catch 절을 지원하지 않습니다.

스크립트를 V8로 이전할 때는 catch 조건문을 catch 본문 안으로 이동하세요.

// Rhino runtime

try {
  doSomething();
} catch (e if e instanceof TypeError) {  // Don't use
  // Handle exception
}
      
// V8 runtime
try {
  doSomething();
} catch (e) {
  if (e instanceof TypeError) {
    // Handle exception
  }
}

Object.prototype.toSource() 사용하지 않기

JavaScript 1.3에는 ECMAScript 표준에 포함된 적이 없는 Object.prototype.toSource() 메서드가 포함되어 있었습니다. V8 런타임에서는 지원되지 않습니다.

스크립트를 V8로 이전할 때는 코드에서 Object.prototype.toSource() 사용을 삭제하세요.

그 밖의 차이점

스크립트 실패를 유발할 수 있는 위의 비호환성 외에도 수정하지 않으면 예기치 않은 V8 런타임 스크립트 동작이 발생할 수 있는 몇 가지 다른 차이점이 있습니다.

다음 섹션에서는 이러한 예기치 않은 상황을 방지하기 위해 스크립트 코드를 업데이트하는 방법을 설명합니다.

언어별 날짜 및 시간 형식 조정

Date 메서드 toLocaleString(), toLocaleDateString(), toLocaleTimeString()는 Rhino와 비교했을 때 V8 런타임에서 다르게 작동합니다.

Rhino에서 기본 형식은 긴 형식이며 전달된 매개변수는 무시됩니다.

V8 런타임에서 기본 형식은 짧은 형식이며 전달된 매개변수는 ECMA 표준에 따라 처리됩니다 (자세한 내용은 toLocaleDateString() 문서 참고).

스크립트를 V8로 이전할 때는 지역별 날짜 및 시간 메서드의 출력에 관한 코드의 예상치를 테스트하고 조정하세요.

// Rhino runtime
var event = new Date(
  Date.UTC(2012, 11, 21, 12));

// Outputs "December 21, 2012" in Rhino
console.log(event.toLocaleDateString());

// Also outputs "December 21, 2012",
//  ignoring the parameters passed in.
console.log(event.toLocaleDateString(
    'de-DE',
    { year: 'numeric',
      month: 'long',
      day: 'numeric' }));
// V8 runtime
var event = new Date(
  Date.UTC(2012, 11, 21, 12));

// Outputs "12/21/2012" in V8
console.log(event.toLocaleDateString());

// Outputs "21. Dezember 2012"
console.log(event.toLocaleDateString(
    'de-DE',
    { year: 'numeric',
      month: 'long',
      day: 'numeric' }));
      

Error.fileNameError.lineNumber 사용하지 않기

V8 런타임에서 표준 JavaScript Error 객체는 fileName 또는 lineNumber를 생성자 매개변수 또는 객체 속성으로 지원하지 않습니다.

스크립트를 V8로 이전할 때는 Error.fileNameError.lineNumber에 대한 종속 항목을 삭제하세요.

대안은 Error.prototype.stack를 사용하는 것입니다. 이 스택도 비표준이지만 V8에서 지원됩니다. 두 플랫폼에서 생성되는 스택 트레이스의 형식은 약간 다릅니다.

// Rhino runtime Error.prototype.stack
// stack trace format
at filename:92 (innerFunction)
at filename:97 (outerFunction)
// V8 runtime Error.prototype.stack
// stack trace format
Error: error message
at innerFunction (filename:92:11)
at outerFunction (filename:97:5)
      

문자열화된 enum 객체의 처리 조정

원래 Rhino 런타임에서는 enum 객체에서 JavaScript JSON.stringify() 메서드를 사용하면 {}만 반환됩니다.

V8에서는 열거형 객체에 동일한 메서드를 사용하면 열거형 이름이 반환됩니다.

스크립트를 V8로 이전할 때 열거형 객체에서 JSON.stringify()의 출력에 관한 코드의 예상치를 테스트하고 조정하세요.

// Rhino runtime
var enumName =
  JSON.stringify(Charts.ChartType.BUBBLE);

// enumName evaluates to {}
// V8 runtime
var enumName =
  JSON.stringify(Charts.ChartType.BUBBLE);

// enumName evaluates to "BUBBLE"

정의되지 않은 매개변수 처리 조정

원래 Rhino 런타임에서는 undefined를 메서드에 매개변수로 전달하면 문자열 "undefined"가 해당 메서드에 전달되었습니다.

V8에서는 메서드에 undefined을 전달하는 것이 null을 전달하는 것과 같습니다.

스크립트를 V8로 이전할 때는 undefined 매개변수에 관한 코드의 예상치를 테스트하고 조정하세요.

// Rhino runtime
SpreadsheetApp.getActiveRange()
    .setValue(undefined);

// The active range now has the string
// "undefined"  as its value.
      
// V8 runtime
SpreadsheetApp.getActiveRange()
    .setValue(undefined);

// The active range now has no content, as
// setValue(null) removes content from
// ranges.

전역 this 처리 조정

Rhino 런타임은 이를 사용하는 스크립트에 대한 암시적 특수 컨텍스트를 정의합니다. 스크립트 코드는 실제 전역 this와는 다른 이 암시적 컨텍스트에서 실행됩니다. 즉, 코드에서 '전역 this'에 대한 참조는 실제로 스크립트에 정의된 코드와 변수만 포함하는 특수 컨텍스트로 평가됩니다. 기본 제공 Apps Script 서비스 및 ECMAScript 객체는 이 this 사용에서 제외됩니다. 이 상황은 다음과 같은 JavaScript 구조와 유사했습니다.

// Rhino runtime

// Apps Script built-in services defined here, in the actual global context.
var SpreadsheetApp = {
  openById: function() { ... }
  getActive: function() { ... }
  // etc.
};

function() {
  // Implicit special context; all your code goes here. If the global this
  // is referenced in your code, it only contains elements from this context.

  // Any global variables you defined.
  var x = 42;

  // Your script functions.
  function myFunction() {
    ...
  }
  // End of your code.
}();

V8에서는 암시적 특수 컨텍스트가 삭제됩니다. 스크립트에 정의된 전역 변수와 함수는 Math, Date과 같은 내장 Apps Script 서비스 및 ECMAScript 내장 기능 옆의 전역 컨텍스트에 배치됩니다.

스크립트를 V8로 이전할 때는 전역 컨텍스트에서 this 사용에 관한 코드의 기대치를 테스트하고 조정하세요. 대부분의 경우 코드가 전역 this 객체의 키 또는 속성 이름을 검사하는 경우에만 차이가 명확하게 드러납니다.

// Rhino runtime
var myGlobal = 5;

function myFunction() {

  // Only logs [myFunction, myGlobal];
  console.log(Object.keys(this));

  // Only logs [myFunction, myGlobal];
  console.log(
    Object.getOwnPropertyNames(this));
}





      
// V8 runtime
var myGlobal = 5;

function myFunction() {

  // Logs an array that includes the names
  // of Apps Script services
  // (CalendarApp, GmailApp, etc.) in
  // addition to myFunction and myGlobal.
  console.log(Object.keys(this));

  // Logs an array that includes the same
  // values as above, and also includes
  // ECMAScript built-ins like Math, Date,
  // and Object.
  console.log(
    Object.getOwnPropertyNames(this));
}

라이브러리에서 instanceof 처리 조정

다른 프로젝트의 함수에서 매개변수로 전달되는 객체의 라이브러리에서 instanceof를 사용하면 거짓 음성이 발생할 수 있습니다. V8 런타임에서 프로젝트와 라이브러리는 서로 다른 실행 컨텍스트에서 실행되므로 전역 변수와 프로토타입 체인이 서로 다릅니다.

이는 라이브러리가 프로젝트에서 생성되지 않은 객체에 instanceof를 사용하는 경우에만 해당합니다. 프로젝트 내에서 동일한 스크립트 또는 다른 스크립트에 관계없이 프로젝트에서 생성된 객체에 사용하면 예상대로 작동합니다.

V8에서 실행되는 프로젝트가 스크립트를 라이브러리로 사용하는 경우 스크립트가 다른 프로젝트에서 전달되는 매개변수에 instanceof를 사용하는지 확인합니다. instanceof 사용을 조정하고 사용 사례에 따라 다른 실행 가능한 대안을 사용하세요.

a instanceof b의 한 가지 대안은 전체 프로토타입 체인을 검색할 필요가 없고 생성자만 확인하면 되는 경우에 a의 생성자를 사용하는 것입니다. 사용: a.constructor.name == "b"

프로젝트 A가 프로젝트 B를 라이브러리로 사용하는 프로젝트 A와 프로젝트 B를 고려해 보겠습니다.

//Rhino runtime

//Project A

function caller() {
   var date = new Date();
   // Returns true
   return B.callee(date);
}

//Project B

function callee(date) {
   // Returns true
   return(date instanceof Date);
}

      
//V8 runtime

//Project A

function caller() {
   var date = new Date();
   // Returns false
   return B.callee(date);
}

//Project B

function callee(date) {
   // Incorrectly returns false
   return(date instanceof Date);
   // Consider using return (date.constructor.name ==
   // Date) instead.
   // return (date.constructor.name == Date) -> Returns
   // true
}

또 다른 대안은 기본 프로젝트에서 instanceof를 확인하는 함수를 도입하고 라이브러리 함수를 호출할 때 다른 매개변수 외에 함수를 전달하는 것입니다. 전달된 함수를 사용하여 라이브러리 내에서 instanceof를 확인할 수 있습니다.

//V8 runtime

//Project A

function caller() {
   var date = new Date();
   // Returns True
   return B.callee(date, date => date instanceof Date);
}

//Project B

function callee(date, checkInstanceOf) {
  // Returns True
  return checkInstanceOf(date);
}
      

공유되지 않은 리소스를 라이브러리에 전달하는 방식 조정

기본 스크립트에서 라이브러리로 공유되지 않은 리소스를 전달하는 방식은 V8 런타임에서 다릅니다.

Rhino 런타임에서는 공유되지 않은 리소스를 전달할 수 없습니다. 라이브러리는 자체 리소스를 대신 사용합니다.

V8 런타임에서는 공유되지 않은 리소스를 라이브러리에 전달하는 것이 작동합니다. 라이브러리가 전달된 비공유 리소스를 사용합니다.

공유되지 않은 리소스를 함수 매개변수로 전달하지 마세요. 항상 리소스를 사용하는 동일한 스크립트에서 공유되지 않은 리소스를 선언합니다.

프로젝트 A가 프로젝트 B를 라이브러리로 사용하는 프로젝트 A와 프로젝트 B를 고려해 보겠습니다. 이 예시에서 PropertiesService은 공유되지 않는 리소스입니다.

// Rhino runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-B
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

독립형 스크립트 액세스 권한 업데이트

V8 런타임에서 실행되는 독립형 스크립트의 경우 스크립트의 트리거가 제대로 작동하려면 사용자에게 스크립트에 대한 보기 액세스 권한을 최소한 제공해야 합니다.