clasp + TypeScriptで作成したときの変換の挙動をメモする

zennに書こうと思ったけど、ほとんど自明なことなのでブログにメモとして残します。

残課題はarrayの関数の変換

claspとは

googleが作成しているGASをローカル上で開発できるようにしているツールです。 - https://github.com/google/clasp

TypeScriptにも対応しているため、型の恩恵等を受けることができます。 - https://github.com/google/clasp/blob/master/docs/typescript.md - 内部的にはts2gasを利用しているそうです。 - https://github.com/grant/ts2gas

初期設定

まずはプロジェクトの初期設定をします。 プロジェクトを作成して、型定義のnpmを追加します。 clasp系の設定はリンク等を参照してください。 - https://qiita.com/HeRo/items/4e65dcc82783b2766c03

npm init -y
mkdir src
npm i -S @types/google-apps-script
clasp create --type standalone --rootDir ./src
  • 公式のドキュメントにしたがって、tsconfig.jsonも設定します。
{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true
  }
}
  • 最初の挙動を確かめるために、サンプルにあったソースをそのまま利用します。
const greeter = (person: string) => {
  return `Hello, ${person}!`;
}

function testGreeter() {
  const user = 'Grant';
  Logger.log(greeter(user));
}
  • GASにプッシュします。
clasp push
  • GASのコンソールから結果を確認してみます。
  • constがvarになっているのと文字の連結が変わっています。
// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var greeter = function (person) {
    return "Hello, " + person + "!";
};
function testGreeter() {
    var user = 'Grant';
    Logger.log(greeter(user));
}

各種挙動を確認する

  • class等の記法がどのような形で変換されるのかを見ていきます。

class

  • 変換前
class Clazz {
  constructor(private member: number) {}

    get item() {
        return this.getItem();
    }

    getMember() {
        return this.getMember();
    }

    private getItem() {
        return 'item';
    }

    static factory(member: number) {
        return new Clazz(member);
    }
}
  • 変換後。prototypeベースに置き換わっています。
// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var Clazz = /** @class */ (function () {
    function Clazz(member) {
        this.member = member;
    }
    Object.defineProperty(Clazz.prototype, "item", {
        get: function () {
            return this.getItem();
        },
        enumerable: false,
        configurable: true
    });
    Clazz.prototype.getMember = function () {
        return this.getMember();
    };
    Clazz.prototype.getItem = function () {
        return 'item';
    };
    Clazz.factory = function (member) {
        return new Clazz(member);
    };
    return Clazz;
}());

import/export

変換前

  • module.ts
export const hoge = () => 'hoge';

export const constTest = 'test'
  • sample.ts
import { hoge, constTest } from "./module";

function test() {
    console.log(hoge());
    console.log(constTest);
}

変換後

  • module.gs
// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var exports = exports || {};
var module = module || { exports: exports };
exports.constTest = exports.hoge = void 0;
var hoge = function () { return 'hoge'; };
exports.hoge = hoge;
exports.constTest = 'test';
  • sample.gs
// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var exports = exports || {};
var module = module || { exports: exports };
//import { constTest, hoge } from "./module";

hoge();
  • sample.gsは実行すると失敗します
ReferenceError: constTest is not defined
  • よくよく見てみると、export const で定義したfunctionは成功しますが、変数はエラーになっています。
  • これは、module.tsでhogeはvarとして定義されているために、global変数だとみなされているため参照できるようになっています。
  • ただし、constTestはexportsのメンバ変数として定義されているので、exports.constTestのように呼び出ししないとうまく動作しません

    • にしても、exportsがグローバルなので、変数の競合があると意図しない動きになると思います。
  • 解消方法としては以下の3点があります。ここでは詳細では取り上げません。興味があれば公式のドキュメントを見てみてください。

    • exportsを明示的に変数として定義して、そこから読み込むようにする
      • なんかトリッキーな書き方ですね。複数のモジュールをimportしたいときにうまく動かなそうな感じがする。
    • namespaceを利用する
      • namespace使っていいのか問題はあるが、割と簡単。
    • 先にwebpack等でトランスパイルしてからGASにプッシュする
      • GAS上で見にくくなってもいいならよさそう

namespace

変換前

namespace Module {
  export function foo() {}
  function bar() {}  // this function can only be addressed from within the `Module` namespace
}

変換後

// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var Module;
(function (Module) {
    function foo() { }
    Module.foo = foo;
    function bar() { } // this function can only be addressed from within the `Module` namespace
})(Module || (Module = {}));
  • Moduleという変数が定義されていて、それの変数として、foo、barが定義されています。
  • そのため、moduleは外からの呼び出しにも対応できます。

array

変換前

const number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

number.find((i) => i === 10);
number.findIndex((i) => i === 10);
number.filter((i) => i % 2);
number.map((i) => i ^ i);
number.reduce((pre, cur) => pre + cur, 0);
number.some((i) => i % 2);
number.slice(0);
[...number].sort((a, b) => b - a);
number.forEach(a => a);
number.every(i => true);

変換後

// Compiled using clasp-test 1.0.0 (TypeScript 4.3.5)
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
var number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
number.find(function (i) { return i === 10; });
number.findIndex(function (i) { return i === 10; });
number.filter(function (i) { return i % 2; });
number.map(function (i) { return i ^ i; });
number.reduce(function (pre, cur) { return pre + cur; }, 0);
number.some(function (i) { return i % 2; });
number.slice(0);
__spreadArray([], number).sort(function (a, b) { return b - a; });
number.forEach(function (a) { return a; });
number.every(function (i) { return true; });
  • スプレッド変数のみ変換がかかっていて、ほかのarrayはそのままになっています。
  • ES3相当の変換なのに、ES3で存在しないはずの変数が残っている。。ここではまったので別の機会に解消の仕方を書きます。