Google Apps Script が V8 ランタイムに対応!最新 ECMAScript が使えるように

目次



Apps Script での V8 ランタイム概要


Google Apps Script および JavaScript では, スクリプトのコードを解析・実行する JavaScript エンジンが "ランタイム" (またはランタイム環境) に含まれます. ランタイムは, メモリへのアクセス方法やプログラムと OS が相互作用する方法, どのような構文が記述できるかといったルールを提供するもので, Web ブラウザはそれぞれ JavaScript のランタイムを搭載しています.

これまで Apps Script は Mozilla の Rhino JavaScript interpreter をランタイムとして採用していました. この Rhino は Apps Script で開発者のコードを実行するのに非常に便利なものでしたが, Apps Script で使用できる JavaScript のバージョンが ES5 から変更できないという難点の原因でもありました.

これを解決するため, 今回の変更から Apps Script は Chrome や Node.js にも使用されている V8 ランタイムを使用します. これにより, 最新の JavaScript の構文と機能を利用するために 今ある Apps Script のスクリプトを V8 に移行する ことができます.

まずは V8 によって新しく使えるようになった機能と Apps Script のスクリプトで V8 を有効にする方法を説明します. スクリプトを V8 に移行する では今ある Apps Script のスクリプトを V8 を使用できるように移行するための手順を説明します.

V8 ランタイムの機能

V8 ランタイムを使用するスクリプトは以下の機能を使用することができます.

最新の ECMAScript の構文

V8 ランタイムを使用するスクリプトでは最新の ECMAScript の構文を使用できます. これには letconst およびその他の一般的な機能が含まれます.

V8 ランタイムを使用して改善できる一般的な構文の一覧は V8 構文の例 をご覧ください.

[注意] ES6 のモジュールはまだサポートされていません

関数の検出の改善

V8 によって Apps Script の関数検出が改善されました. 新しいランタイムでは以下のような形式で定義された関数を認識できます.

      function normalFunction() {}
      async function asyncFunction() {}
      function* generatorFunction() {}

      var varFunction = function() {}
      let letFunction = function() {}
      const constFunction = function() {}

      var namedVarFunction = function alternateNameVarFunction() {}
      let namedLetFunction = function alternateNameLetFunction() {}
      const namedConstFunction = function alternateNameConstFunction() {}

      var varAsyncFunction = async function() {}
      let letAsyncFunction = async function() {}
      const constAsyncFunction = async function() {}

      var namedVarAsyncFunction = async function alternateNameVarAsyncFunction() {}
      let namedLetAsyncFunction = async function alternateNameLetAsyncFunction() {}
      const namedConstAsyncFunction = async function alternateNameConstAsyncFunction() {}

      var varGeneratorFunction = function*() {}
      let letGeneratorFunction = function*() {}
      const constGeneratorFunction = function*() {}

      var namedVarGeneratorFunction = function* alternateNameVarGeneratorFunction() {}
      let namedLetGeneratorFunction = function* alternateNameLetGeneratorFunction() {}
      const namedConstGeneratorFunction = function* alternateNameConstGeneratorFunction() {}

      var varLambda = () => {}
      let letLambda = () => {}
      const constLambda = () => {}

      var varAsyncLambda = async () => {}
      let letAsyncLambda = async () => {}
      const constAsyncLambda = async () => {}

トリガーとコールバックからオブジェクトのメソッドを呼び出す

V8 を使用したスクリプトでは, 既にライブラリのメソッドを呼び出せる場所からオブジェクトのメソッドやクラスの静的メソッドを呼び出すことができます. これには以下の場所が含まれます.
次の V8 のサンプルは Google スプレッドシート内のメニューアイテムを構成するときにオブジェクトのメソッドを使用する方法を示しています.

function onOpen() {
  var ui = SpreadsheetApp.getUi(); // Or DocumentApp, SlidesApp, or FormApp.
  ui.createMenu('Custom Menu')
      .addItem('First item', 'menu.item1')
      .addSeparator()
      .addSubMenu(ui.createMenu('Sub-menu')
          .addItem('Second item', 'menu.item2'))
      .addToUi();
}

var menu = {
  item1: function() {
    SpreadsheetApp.getUi().alert('You clicked: First item');
  },
  item2: function() {
    SpreadsheetApp.getUi().alert('You clicked: Second item');
  }
}

ログの UI 変更

Apps Script には Logger サービスconsole クラスの 2 つのログサービスがあります. これらはどちらも同じ Stackdriver Logging サービス にログを記録します.

Rhino ランタイムを使用している場合, Apps Script のエディタで 表示 > ログ を選んで見られるのは Logger サービス によって記録された最近のログのみで, console クラスによって記録されたログを見るには 表示 > Stackdriver Logging を選択する必要があります.

V8 ランタイムを使用したスクリプトでは, スクリプトエディタの 表示 > ログ メニューアイテムには現在のセッションでの最新の実行の Logger と console 両方の結果が表示されます. なお従来のログも 表示 > Stackdriver Logging から確認できます.

実行の UI 変更

V8 の導入により実行トランスクリプトの場所が変更されました. Rhino ランタイムを使用している場合, 実行トランスクリプトは Apps Script エディタの 表示 > 実行トランスクリプト を選ぶことで確認できます. 実行トランスクリプトダイアログには Apps Script サービスの呼び出しとそれに関連するタイムスタンプの一覧である実行トランスクリプトが表示されます.

V8 ランタイムを使用している場合には, 実行トランスクリプトは無効となりまが, かわりに Apps Script エディタの 表示 > 実行数 を選ぶことで表示されるそのプロジェクトの Executions パネル から, Apps Script ダッシュボード で実行履歴を見ることができます. この画面から実行を選択し, その実行に関連付けられた Stackdriver ログを確認できます. Rhino での実行トランスクリプトとは違い, Executions パネルでは (最近の実行のみではなく) 複数の実行履歴を見ることができます.

[備考] Executions パネルは Apps Script サービスのそれぞれの呼び出しに対応するタイムスタンプを持たないため, 適切なログメッセージを記録するには console サービスを使用することを推奨します. console によって作成されたログはすべて Executions パネルに表示されます.

V8 の構文例

V8 ランタイムによって使用できるようになる一般的な構文機能の簡易的な一覧をいかに示します.

let および const

let および const キーワードによりそれぞれブロック内でのローカル変数およびブロック内での定数を定義できます.

// V8 runtime
let s = "hello";
if (s === "hello") {
  let s = "world";
  console.log(s);  // Prints "world"
}
console.log(s);  // Prints "hello"

const N = 100;
N = 5; // Results in TypeError

アロー関数

アロー関数 は関数をコンパクトに定義する構文です.
// Rhino runtime
function square(x) {
  return x * x;
}

console.log(square(5));  // Outputs 25
// V8 runtime
const square = x => x * x;
console.log(square(5));  // Outputs 25

// Outputs [1, 4, 9]
console.log([1, 2, 3].map(x => x * x));

クラス

クラス は継承を使い, コードを概念的に整理するために使用できます. V8 でのクラスは主に, JavaScript のプロトタイプベースの継承を介した糖衣構文です.
// V8 runtime
class Rectangle {
  constructor(width, height) { // class constructor
    this.width = width;
    this.height = height;
  }

  logToConsole() { // class method
    console.log(`Rectangle(width=${this.width}, height=${this.height})`);
  }
}

const r = new Rectangle(10, 20);
r.logToConsole();  // Outputs Rectangle(width=10, height=20)

分割代入

分割代入 は配列およびオブジェクトにより得られる値を明確な変数に展開するための表現です.
// Rhino runtime
var data = {a: 12, b: false, c: 'blue'};
var a = data.a;
var c = data.c;
console.log(a, c);  // Outputs 12 "blue"

var array = [1, 2, 3];
var x = a[0];
var y = a[1];
var z = a[2];
console.log(x, y, z);  // Outputs 1 2 3
// V8 runtime
var data = {a: 12, b: false, c: 'blue'};
var {a, c} = data;
console.log(a, c);  // Outputs 12 "blue"


var array = [1, 2, 3];
var [x, y, z] = array;
console.log(x, y, z);  // Outputs 1 2 3

テンプレート 文字列

テンプレート文字列 は埋め込みが可能な文字列リテラルで, 文字列の結合において複雑な表記を避けることに使用できます.
// Rhino runtime
var name =
  'Hi ' + first + ' ' + last + '.';
var url =
  'http://localhost:3000/api/messages/'
  + id;
// V8 runtime
var name = `Hi ${first} ${last}.`;
var url =
  `http://localhost:3000/api/messages/${id}`;

デフォルト引数

デフォルト引数 を使用することで関数の宣言時に引数のデフォルトの値を設定できます. これにより, 渡されなかった引数に対して明示的にデフォルトの値を与える必要がなくなり, 関数の本文のコードをシンプルにできます.
// Rhino runtime
function hello(greeting, name) {
    greeting = greeting || "hello";
    name = name || "world";
    console.log(
        greeting + " " + name + "!");
}

hello();  // Outputs "hello world!"
// V8 runtime
var hello =
  function(greeting="hello", name="world") {
      console.log(
        greeting + " " + name + "!");
  }

hello();  // Outputs "hello world!"

複数行文字列

テンプレート文字列 と同じ構文を使用することで 複数行文字列 を定義できます. テンプレート文字列と同じく, 複雑な文字列結合を避けてシンプルに文字列を定義することができます.

V8 ランタイムを有効化する

スクリプトで Rhino ランタイムを使用している場合, 以下の手順で V8 ランタイムに切り替えられます.

  1. Apps Script エディタでスクリプトを開く.
  2. 実行 > Chrome V8 を搭載した新しい Apps Script ランタイムを有効にする を選択.
  3. スクリプトを保存する.
かわりに直接 スクリプトのマニフェストを編集 することでもスクリプトのランタイムを選択できます.
  1. Apps Script エディタでスクリプトを開く.
  2. 表示 > マニフェスト ファイルを表示 を選択.
  3. マニフェストファイル appsscript.json でフィールド runtimeVersionV8 に設定する.
  4. スクリプトを保存する.
スクリプトを V8 に移行する ではスクリプトが V8 で正常に動作するかを確認するために推奨される手順を説明しています.

Rhino ランタイムを有効にする

V8 ランタイムを使用していて, もとの Rhino ランタイムを使用する必要がある場合は以下の手順を行ってください.

  1. Apps Script エディタでスクリプトを開く.
  2. 実行 > Chrome V8 を搭載した新しい Apps Script ランタイムを無効にする を選択.
かわりに スクリプトのマニフェストを編集 してフィールド runtimeVersionDEPRECATED_ES5 に設定することもできます.

今あるスクリプトを移行するには

スクリプトを V8 に移行する にて今あるスクリプトを V8 に移行するために必要な手順を説明しています. この手順には V8 ランタイムを有効にする手順および既知の互換性の問題をチェックする手順が含まれます.

バグを報告するには

サポートガイド にて Stack Overflow でコードを作成する上でのサポートを受ける方法, 現存する問題のレポートを調べる方法, 新しいバグを報告する方法, および新しい機能をリクエストする方法を紹介しています.



スクリプトを V8 に移行する

[原文: Migrating scripts to the V8 runtime]

Rhino ランタイムを使用しているスクリプトがあり, V8 の構文と機能を使用できるようにしたい場合は, スクリプトを V8 に "移行" する必要があります.

Rhino ランタイムを使用しているスクリプトのほとんどは特別な調整なしに V8 ランタイム上で動作します. ほとんどの場合, V8 ランタイムを有効にする だけで V8 の構文と機能を利用する事ができます.

ただし, 細かな 互換性の問題その他の違い により V8 ランタイム上でスクリプトの実行に失敗したり, 予期しない動作をしたりする可能性があります. そのためスクリプトを V8 に移行後すぐにこのような問題がないか探し, 修正する必要があります.

V8 への移行手順

V8 への移行は以下の手順で行ってください.

  1. スクリプトで V8 ランタイムを有効にする.
  2. 互換性の問題 一覧をよく確認し, スクリプトに互換性の問題があるかを調べます. 1 つ以上の問題がある場合はその問題を取り除くか回避できるようにスクリプトのコードを修正してください.
  3. その他の違い 一覧をよく確認し, これらの違いがコードの挙動に影響するかを調べます. 問題がある場合は正常に動作するようスクリプトを修正してください.
  4. 見つけた互換性の問題やその他の違いを修正を行うと, 必要に応じて V8 の構文と機能 を使うようにコードをアップグレードできます.
  5. コードの修正の完了後, 想定通りにスクリプトが動作するか慎重に検証してください.
  6. スクリプトが ウェブ アプリケーションもしくは アドオン の場合, 新しいバージョンを作成 する必要があります. V8 移行後のバージョンをユーザが使用できるようにするには再度このバージョンを公開する必要があります.

互換性の問題

残念ながら, 以前の Rhino をベースとした Apps Script ランタイムはいくつかの ECMAScript に準拠しない挙動を許容していました. V8 は ECMAScript に準拠しているため, これらの従来許容されていた挙動は移行後にはサポートされなくなります. この問題を修正せずに V8 ランタイムを有効にした場合, スクリプトはエラーを引き起こしたり間違った動作をしたりします.

以下には V8 への移行にともない発生するこれらの問題と, 修正するための手順を記載しています.

for each(変数 in オブジェクト) を使用しない

for each (変数 in オブジェクト) 文は JavaScript 1.6 で追加されましたが, for...of 構文に置き換えられました.

スクリプトを V8 に移行する場合は for each (変数 in オブジェクト) は使用しないでください.

代わりに for (変数 in オブジェクト) を使用します.

// 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() を使用しない

JavaScript 1.2 と最新の Rhino ランタイムでは Date.prototype.getYear() は 1900 年〜 1999 年において 2 桁の年を返しますが, その他の日付については 4 桁の年を返します.

V8 ランタイムでは Date.prototype.getYear() は ECMAScript に準拠し, 年から 1900 を引いた値を返します.

スクリプトを V8 に移行する場合は必ず, 日付にかかわらず 4 桁の年を返す Date.prototype.getFullYear() を使用してください.

名前に予約語を使用しない

ECMAScript では関数名や変数名に 予約語 を使用することを禁止しています. Rhino ランタイムでは多くの予約語の使用が許容されていたため, このような予約語がコードで使用されている場合は関数名や変数名を変更する必要があります.

スクリプトを V8 に移行する場合は変数名や関数名に 予約語 を使用しないでください. また, 予約語を変数名や関数名に使用している場合は変更してください. よく使用される予約語は class, import, export です.

[備考] どちらのランタイムでも, オブジェクト内では予約語を利用できます.
function class() {}     // Syntax error in V8.
var obj = { class: 1 }; // Allowed.

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 オブジェクトを使用しない




__iterator__ を用いた独自のイテレータ関数を作成しない





解析される前に関数を呼び出さない




条件付き catch 句を使用しない





Object.prototype.toSource() を使用しない






その他の違い




ロケール固有の日付と時間の形式を修正する





Error.fileName および Error.lineNumber を使用しない





文字列に変換された enum オブジェクトの扱いを修正する





未定義の引数の扱いを修正する




グローバルな this の扱いを修正する






コメント