電波ビーチ

☆(ゝω・)v

Google Apps Scriptでは現時点でクラスフィールド未対応

どういうこと

適当なApps Scriptオンラインエディタで試してみてください。保存時にエラーになるはず(2024/10/20 現在)

// クラスフィールドでの定義はエラーになる
class Human{
  static category;
}

// constructorでの定義だと大丈夫
/*
class Human{
  constructor(){
    this.category = "animal";
  }
}
*/

経緯

基本的にApps Scriptの開発はclaspで行っていますが、とあるコードでclasp pushしたらエラーが返ってきました。

⠧ Pushing files…Push failed. Errors:
GaxiosError: Syntax error: ParseError: Unexpected token ; line: 56 file: main.gs
  ...略
  response: {
 ...略
  },
  code: 400,
  errors: [
    {
      message: 'Syntax error: ParseError: Unexpected token ; line: 56 file: main.gs',
      domain: 'global',
      reason: 'badRequest'
    }
  ]
}

当然のようにminifyをしていた(esbuild-gas-pluginを使っています)のでいったん解除してちまちま追ってみると、どうやらクラス構文の箇所でエラーが出ている様子。これは本題とは関係ないですがこちらの記事からお借りしたコードをほぼそのまま使ったものです。

qiita.com

export class Failure<E extends Error> {
  readonly error: E;

  constructor(error: E) {
    this.error = error;
  }
  isSuccess(): this is Success<unknown> {
    return false;
  }
  isFailure(): this is Failure<E> {
    return true;
  }
}

export class ErrorResponse extends Error {
  readonly name: string;
  readonly message: string;
  readonly stack?: string;

  constructor(
    readonly functionName: string = 'unknown functionName',
    readonly statusCode: number = 500,
    readonly code: ErrorCode = 'APP_UNKNOWN_ERROR',
    readonly error: Error = new Error('unknown error'),
  ) {
    super();
    this.name = error.name;
    this.message = error.message;
    this.stack = error.stack;
  }
}

そんでもって、わたしの環境の設定でビルドすると、以下のようなコードが生成されていました。

  // src/server/types.ts
  var Failure = class {
    error;
    constructor(error) {
      this.error = error;
    }
    isSuccess() {
      return false;
    }
    isFailure() {
      return true;
    }
  };
  var ErrorResponse = class extends Error {
    constructor(
      functionName = 'unknown functionName',
      statusCode = 500,
      code = 'APP_UNKNOWN_ERROR',
      error = new Error('unknown error'),
    ) {
      super();
      this.functionName = functionName;
      this.statusCode = statusCode;
      this.code = code;
      this.error = error;
      this.name = error.name;
      this.message = error.message;
      this.stack = error.stack;
    }
    name;
    message;
    stack;
  };

これはクラス定義がクラスフィールドで書かれたもので、ES2022で導入された記法のようです。ChatGPTに教えてもらいました。いつもありがとうね。

どうやらGoogle Apps Scriptのランタイムではまだクラスフィールドに未対応の様子です。

対応

ビルド用のスクリプト(さっきも書きましたが、わたしはesbuild-gas-pluginを使ってます)でtargetをES2021と明示的に書き換えると、エラーなくpushできました。

import esbuild from 'esbuild';
import { GasPlugin } from 'esbuild-gas-plugin';

esbuild
  .build({
    entryPoints: ['src/server/main.ts'],
    bundle: true,
    outfile: 'dist/main.js',
    minify: true,
    // format: 'esm', export文が生成されてしまうのでやめとく
    logLevel: 'info',
    target: 'ES2021', // Apps Scriptでは現状、クラスフィールド未対応らしい
    plugins: [GasPlugin],
  })
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });

いやーまいった。配列やオブジェクトのメソッド周りは意外と最近のECMAScriptに追従しているイメージだったんですが(Array.prototype.toSorted(ES2022)とかArray.prototype.toSpliced(ES2023)とか、Set.prototype.difference(ES2024)とかは使用可能なのを確認してます)、クラス構文でエラーになるとは。特殊な環境なのでいろいろ罠がありますね...