スクリプトを V8 ランタイムに移行する

Rhino ランタイムは、2026 年 1 月 31 日以降ご利用いただけなくなります。Rhino ランタイムを使用する既存のスクリプトがある場合は、スクリプトを V8 に移行する必要があります。

多くの場合、スクリプトに V8 構文と機能を追加するための唯一の前提条件は、V8 ランタイムを有効にすることです。ただし、V8 ランタイムでスクリプトが失敗したり、予期しない動作をしたりする可能性がある非互換性その他の違いがいくつかあります。スクリプトを V8 を使用するように移行する際は、スクリプト プロジェクトでこれらの問題を探し、見つかった問題を修正する必要があります。

V8 移行手順

スクリプトを V8 に移行する手順は次のとおりです。

  1. スクリプトの V8 ランタイムを有効にしますruntimeVersion は、Apps Script プロジェクトのマニフェストを使用して確認できます。
  2. 次の非互換性をよく確認してください。スクリプトを調べて、互換性のないものが含まれているかどうかを確認します。互換性のないものが 1 つ以上含まれている場合は、スクリプト コードを調整して問題を解消または回避します。
  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 年の年に対しては 2 桁の年を返しますが、その他の日付に対しては 4 桁の年を返します。これは JavaScript 1.2 以前の動作です。

V8 ランタイムでは、ECMAScript 標準で定められているとおり、Date.prototype.getYear() は年から 1900 を引いた値を返します。

スクリプトを V8 に移行する場合は、日付に関係なく 4 桁の年を返す Date.prototype.getFullYear() を常に使用してください。

予約済みキーワードを名前として使用しない

ECMAScript では、関数名と変数名で特定の予約キーワードを使用することが禁止されています。Rhino ランタイムではこれらの単語の多くが許可されていましたが、コードで使用されている場合は、関数または変数の名前を変更する必要があります。

スクリプトを V8 に移行する場合は、予約済みキーワードのいずれかを使用して変数や関数に名前を付けないでください。キーワード名を使用しないように、変数または関数の名前を変更します。キーワードを名前として使用する一般的な例は、classimportexport です。

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 には Object.prototype.toSource() メソッドが含まれていましたが、これは ECMAScript 標準の一部ではありませんでした。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 でサポートされています。2 つのプラットフォームで生成されるスタック トレースの形式は若干異なります。

// 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)
      

文字列化された列挙型オブジェクトの処理を調整

元の Rhino ランタイムでは、列挙型オブジェクトで 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 では、暗黙的な特別なコンテキストが削除されています。スクリプトで定義されたグローバル変数と関数は、組み込みの Apps Script サービスや MathDate などの 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 ランタイムで実行されるスタンドアロン スクリプトの場合、スクリプトのトリガーが適切に動作するようにするには、ユーザーにスクリプトの閲覧権限を付与する必要があります。