Google Apps Script プロジェクトの開発をローカルに落としてモダンな開発をしてみる

# gasは便利だけど管理が大変
Google Apps Script(通称gas)は大変便利なのであります。
便利であるがゆえに小さなプロジェクトが乱立しがちなのであります。
動かないスクリプトが出てきてやばいなと思った時にはすでに遅し。メンテナンス困難な状況になっています。

どうにかならないものかと調査してみたところ プロジェクトをローカルに落として作業できるということではありませんか。

プロジェクトをローカルに落とすことによっていくつかのメリットがあります。

- ソースコードを git で管理できる
- Node.js 近辺の資産を流用できる
- es6で書くことができる (babelを挟んでトランスパイルすれば)

メリットしかないような気がしてきたぞ。

# 必要なもの
- Node.js
- git 
- Google のアカウント
- githubのアカウント (もしソースコードをgithubで管理するなら)

# プロジェクトの確認 
[プロジェクト一覧](https://script.google.com/home)からあなたが管理している google apps script のプロジェクトを確認することができます。もちろんこの画面から新規にプロジェクトを作成することもできます。プロジェクトはコマンドラインから作成することもできるのでここは確認だけとしておきます 

# ローカルに作業場所を構築する
まずはgithubでリポジトリを作成して作業フォルダを用意。空っぽではありますがリポジトリの内容をダウンロードします。

```
cd work
mkdir test
cd test
git clone https://github.com/yourid/projectname.git。
```

Google Apps Scriptプロジェクトを作成します。
プロジェクトをローカルで作業できるように[clasp](https://github.com/google/clasp)が Google から 提供されています。

```
# claspインストール 
npm install @google/clasp --global
# Google ドライブ上にプロジェクトを作成
clasp create test
```

既にプロジェクトを作成している場合はプロジェクトのプロパティから Project ID を調べて次のようにします。

```
clasp open projectid
clasp pull
```

※ファイル名が衝突していると既存のファイルが上書きされてしまう恐れあり 
なお拡張子は.gsと.jsの間で自動的に変換されます。

# 本番環境へのpush
これでローカルのファイルを Google にアップロードすることができるようになります 

```
clasp push
```

アップロードできるのは JavaScript ファイルと appsscript.json だけです。他のファイルをpushしようとするとエラーではじかれてしまうので.claspignoreで除外しておきます。

```:.claspignore
**/**
!*.js
!appsscript.json
```

[プロジェクト一覧](https://script.google.com/home)からプロジェクトを開いてみてください。ローカルのファイルがアップロードされているでしょうか? 
後は好きなように開発して気が済んだらpushします。

# モダンな開発環境とやらを整える 
何を隠そう Node.js 初体験なので体制が固まるまでずいぶん時間がかかってしまいました。何を組み込んだのかさっぱり忘れてしまいましたがpackage.jsonを見る限り次のライブラリを使っているようです。

```json:package.json
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-register": "^6.26.0",
    "babelify": "^8.0.0",
    "browserify": "^16.2.2",
    "chai": "^4.1.2",
    "gasify": "^0.1.2",
    "gulp": "^3.9.1",
    "mocha": "^5.2.0",
    "sinon": "^6.1.5"
  },
```

package.jsonの該当部分を上記のように編集したら

```
npm install
```
で依存ライブラリをインストールできます。

[Browserify](http://browserify.org/)や[Babel](https://babeljs.io/)と組み合わせればファイルを分割したりクラスをかけるんですね。JavaScript って常識的に動きそうな処理で動かなかったりするのでいまいちとっつきにくいですよね。
gas対応しているのはes5までのようでそれより新しい書き方はできないということでエレガントな記述ができるようになるだけでも随分助かります。
[gasify](https://www.npmjs.com/package/gasify)を使えばbrowserifyでラップされた関数を本番環境でも実行することができます。

babelifyでes6 からes5  へと変換し browserifyでファイル群をパッケージがするんですね。
babelifyで変換する設定は次のような感じ。

```json:package.json
  "babel": {
    "presets": [
      [
        "es2015"
      ]
    ]
  },
```

ソースコードは./src/の下に配置してbrowserifyで./main.jsに出力するようにします。この界隈ではJavaScript コードはmain.js にまとめるのが作法になっているようなのでそれに従うことにします。
このmain.js が main.gs としてプッシュされて本番環境でのプログラムとなります。
ビルドのオプションはこんな感じ。

```json:package.json
  "scripts": {
    "build": "browserify ./src/main.js -t babelify -p gasify --outfile ./main.js",
    "test": "mocha --require babel-register"
  },
```

ビルドは

```
npm run  build
```

でできます。
テストは./test下に配置しておけば

```
npm run test
```

で全ての .js ファイルをテストコードとして自動テスト実行してくれます。

# ハマりポイント
## インポートが動いてくれない
分割したファイルをimportで読み込むとなぜだかエラーが出て動きません。
babelが吐き出したコードなので私のスキルでは対処不能です。
requireにしたところ動いたので当面の間こちらを使って運用することにします。

## Post リクエストで起動できなくなった 
gasで書いた処理を *Web アプリケーションとして導入*することにより、簡易的なrestの Webサーバとすることができます。
Post メソッドで呼び出された時の処理はdoPost 関数に書くことになるのですが、これをmain.js においてしまうと実行されません。
そこでgas.gs ファイルを作って、その処理だけはgasで記述することにしました。

```javascript:gas.gs
function doPost(e) {
 var params = getPostParameters(e);
 var sheetId = params.sheet_id;
 return makeContent(post(sheetId));
}
```

ただしここから main.jsで定義したクラスを参照することはできなかったのでglobal(gasifyがグローバルネームスペースを定義してくれたやつ)に定義した関数を経由して起動することにしました。

```javascript:main.js
global.post = function(sheetId) {
 let tweet = new Tweet();
 return tweet.tweetMemo(sheetId);
}
```

これで独自にクラス宣言したTweetを参照できました。ちゃんと JavaScript を勉強している人ならこんなことしなくても呼び出せると思いますけど…

# もっとハマりポイント
gasの環境に依存した処理をローカルでテストすることができません。
クラスのメソッドになっているとスタブを用意するのも難しくてどうにもならんです。結局ほとんどの処理をテストすることができなかったのでモダンな開発環境とはとても言い難い。何かうまい回避方法はないものなのか。

# 参考にした記事 
- [Google Apps Scriptでrequire()してみる - Qiita](https://qiita.com/fossamagna/items/7c65e249e1e5ecad51ff)
- [【GoogleAppsScript】ES6を使ったGoogle Apps Scriptの開発 - Qiita](https://qiita.com/romukey/items/22eb4ea6d995d5d62f69)
- [Google App Scriptをモダンな開発っぽくする環境を作ってみた(ES6, browserify, eslint, mocha) - nagashigaki](http://frontend-takuyan.hateblo.jp/entry/gas_develop)
- [babel-preset-envを簡単にさわってみた。 - Qiita](https://qiita.com/ryuone/items/13f5d450c3865709ba10)