React で Google Apps Scriptを書く！

Google Apps Script で React を使った開発をしたいと思います。

Google Apps ScriptはGoogle DocsなどGoogleのサービスの拡張やアドオンをJavaScriptで書ける仕組みです。Apps Script は ちょっとした便利ツールの作成には大変よいのですが、本格的に開発を進める場合、素のJavaScriptを書くのはイヤなので、Reactなどのモダンなライブラリを使用したくなります。そこで、モダンなJSのアーキテクチャーにのっとって、Apps Scriptを書いてみたいと思います。
類例の試みはいろいろあるのですが、Reactを導入する実践的なサンプルをあまり見たことがないので、ここでは簡単なサンプルを提供し、内容を解説してみたいと思います。

なお、コードはすべて以下のリポジトリにあげてあります。
https://github.com/takada-at/gas-react

## サーバー・クライアントを整理

少し複雑なのですが、Google Apps Script で動くコードは、「サーバー用のコード」と「クライアント用のコード」にわかれます。ここで「サーバー用のコード」と言っているのは、ドキュメントオープン時やメニューでのコマンド選択時に実行される `.gs`拡張子のついたスクリプトのことです。「Google Apps Script」と言ったときに通常イメージされるのはこれですね。

また、サーバー用のコードでは、Apps Script用の特殊なJavaScript関数を呼び出すことができます。大半はDocsやSpreadsheetの操作用のAPIです。

一方、Apps Scriptでは、サイドメニューやダイアログといったUIを、HTML+JavaScriptで書くことができます。ここでは、このUI用のJavaScriptコードを「クライアント用のコード」と呼びます。クライアント用のコードは基本的に通常のJavaScriptですが、リモートプロシージャコールのような形で、サーバー用のコードの関数を実行できる仕組みが用意されています。

ReactはもちろんUI用のライブラリなので、クライアント側で使用します。言語としてはES2015(ES6)を採用し、サーバー側もクライアント側もwebpack(+babel-loader)を使ってJavaScriptに変換することにします。クライアントに関しては、React用の言語であるJSXも併用します。

| | 言語 | 変換 |
|---|----|-----|
| サーバー | ES2015 | webpackで変換 |
| クライアント | ES2015 + JSX | webpackで変換 |

## サンプルの中身

サンプルとして、Spreadsheetの拡張を用意しました。メニュー(「アドオン」「GasReact」)から「Show Sidebar」を選ぶとサイドバーを表示します。サイドバー内の「Load」をクリックすると、シートのいずれかの列の値を読みとり、UIに表示します。

<img width="494" alt="スクリーンショット 2018-12-19 16.56.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/81883/1fb722f6-924b-e420-b4f7-18cd13c55c17.png">
<img width="338" alt="スクリーンショット 2018-12-19 16.57.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/81883/7fc524be-cae8-e8b4-17b9-e629451fbab7.png">
<img width="316" alt="スクリーンショット 2018-12-19 16.55.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/81883/1d528af5-6bea-f899-1aed-24a5848948ff.png">


## サーバー用コード

サーバー用のコードは[こちら](https://github.com/takada-at/gas-react/tree/master/src/Server)です。中身はシンプルで、メールアドレスを取得する関数と、列を指定してシートの中身を読む関数をAPIとして提供している他、メニューの表示などの設定をしているだけです。

## クライアント用コード
クライアント用のソースコードは[こちら](https://github.com/takada-at/gas-react/tree/master/src/Client)です。React + Redux(+Redux Saga)を使用しています。分量はそこそこありますが、大半はUIと、サーバー側との通信のためのコードなので、詳しく解説しません。

あとで説明するように、サーバー側の関数をリモートプロシージャコールする箇所は、コードを自動生成しています。
## webpackの設定

webpackの設定は[こんな感じ](https://github.com/takada-at/gas-react/blob/master/webpack.config.babel.js)です。Apps Script上で動かすために、[gas-webpack-plugin](https://www.npmjs.com/package/gas-webpack-plugin)、[es3ify-webpack-plugin](https://www.npmjs.com/package/es3ify-webpack-plugin)というプラグインを使用します。gas-webpack-pluginは、Apps Script環境用にグローバールな関数の設定を可能にするプラグイン、es3ify-webpack-pluginは、古いJS環境向けの変換用のプラグインです。

```js
# プラグインの設定
  plugins: [
    new GasPlugin(),
    new Es3ifyPlugin(),
    new webpack.EnvironmentPlugin(['NODE_ENV'])
  ],
```

```js
# エントリポイントの設定。クライアント用コード、サーバー用コードがそれぞれ1ファイルにバンドルされる。
  entry: {
    server: './Server/index.js',
    client: './Client/client.jsx'
  },
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, './build')
  },
```

## クライアント用コードの変換
Apps Script固有の事情として、HTML内で使用するクライアント側コードは、拡張子を`.html`にし、`<script>`タグでマークアップし、HTML断片として作成する必要があります。今回のプロジェクトでは、webpackでクライアント用のバンドルJSファイルを作成したあと、[この変換をかますスクリプト](https://github.com/takada-at/gas-react/blob/master/scripts/upload.js)を別途作成しました。Apps Scriptへのアップロードも、こちらのスクリプトの中でやっています。

```html
# クライアント用コード
# scriptタグでマークアップするとともに、遅延ロードさせている。
<script>
window.addEventListener('load', function() {
  ${content}
}) 
</script>
```

また、サーバーのAPIをリモートプロシージャコールする部分は、お決まりの処理になるので、[スクリプト](https://github.com/takada-at/gas-react/blob/master/scripts/api_gen/api_gen.js)で自動生成できるようにしています。今回のような簡易的プロジェクトだと大げさですが、大規模開発になった場合はおそらくこの種の仕組みがあると便利でしょう。