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

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

Rhino ランタイムを使用する既存のスクリプトがあり、V8 の構文と機能を使用する場合は、スクリプトを V8 に移行する必要があります。

Rhino ランタイムを使用して記述されたほとんどのスクリプトは、調整なしに V8 ランタイムを使用して動作できます。多くの場合、スクリプトに V8 の構文と機能を追加するための前提条件は、V8 ランタイムを有効にすることだけです。

ただし、非互換性その他の違いにより、V8 ランタイムを有効にした後、スクリプトが失敗したり、予期しない動作が発生することがあります。V8 を使用するようにスクリプトを移行する場合、スクリプト プロジェクトでこれらの問題を検索し、修正する必要があります。

V8 の移行手順

スクリプトを V8 に移行するには、次の手順を行います。

  1. スクリプトの V8 ランタイムを有効にします
  2. 下記の互換性の問題をよく確認してください。スクリプトを調べて、互換性がないことを確認します。1 つ以上の非互換性がある場合は、スクリプト コードを調整して、問題を回避または回避します。
  3. 以下に記載されているその他の違いを慎重に確認してください。スクリプトを調べて、リストされた違いがコードの動作に影響するかどうかを確認します。スクリプトを修正して、動作を修正してください。
  4. 検出された非互換性やその他の違いを修正したら、必要に応じて V8 構文とその他の機能を使用するようにコードの更新を開始できます。
  5. コードの調整が完了したら、スクリプトを徹底的にテストして、想定どおりに動作することを確認します。
  6. スクリプトがウェブアプリであるか公開されているアドオンである場合は、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 ランタイムでは、Date.prototype.getYear() は、ECMAScript 標準で義務付けられている年ではなく、年から 1,900 を引いた値を返します。

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

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

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 に移行する際に、キャッチ条件をキャッチ本文内に移動します

// 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() の動作は、Vhi ランタイムと Rhino で異なります。

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 Untime では、標準の JavaScript Error オブジェクトは、コンストラクタ パラメータまたはオブジェクト プロパティとして fileName または lineNumber をサポートしていません。

スクリプトを V8 に移行する際に、Error.fileNameError.lineNumber への依存をすべて削除します

または、Error.prototype.stack を使用する方法もあります。このスタックも標準ではありませんが、Rhino と 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)
      

文字列化された enum オブジェクトの扱いの調整

元の Rhino ランタイムでは、列挙型オブジェクトで JavaScript JSON.stringify() メソッドを使用して、{} のみが返されます。

V8 では、enum オブジェクトに対して同じメソッドを使用することで、列挙型が再実行されます。

スクリプトを 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 ランタイムで実行されるスタンドアロン スクリプトの場合、スクリプトのトリガーが正常に機能するように、少なくともスクリプトに対する表示権限をユーザーに付与する必要があります。