23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GAS を npm パッケージ + Webpack + TypeScript で開発する

Last updated at Posted at 2019-07-31

概要

npm パッケージを使いつつ TypeScript で GAS を書いていく環境を作ったので、整理を兼ねてメモします。

前提

  • clasp を使って GAS を開発している。
  • モダンなフロントエンドの開発手法を理解している。

ゴール

  • GAS をローカル環境上で Babel, TypeScript を使って開発していける環境の構築
  • Spreadsheetの onOpen, onInstall 等にも関数をバインドできる

説明しない事

  • clasp とは
  • Node.js, npm, Babel, TypeScript
  • Webpack の使い方

環境構築手順

clasp に管理させるディレクトリを変更する

GASの開発は普通にやっていくと root ディレクトリ直下に全ファイルが配置される形になるかと思いますが、npmのエコシステムを使う場合は package.jsonnode_modules/ が配置されたりして管理が難しくなっていくので、以下のようにディレクトリ構造を変更してしまいます。

~/
 ├ dist/
 │  └ appsscript.json
 ├ src/
 ├ .clasp.json
 ├ .claspignore
 ├ ...

clasp には dist/ 配下をリリース対象のスクリプトと認識させるため、 .clasp.json"rootDir" というフィールドを追加します。 (https://github.com/google/clasp#project-settings-file-claspjson)

.clasp.json
{
  "scriptId": "<SCRIPT_ID>",
  "rootDir": "./dist"
}

clasp pushdist/ 配下のみが push されるようになります。
確認してませんが、多分 pull も dist 配下に降ってくるんじゃないかと思います。

Babel, TypeScript 周り

最低限以下を入れます。 (Webpack, loader その他は適宜入れてください)

  • @babel/core
  • @babel/preset-env
  • @babel/preset-typescript
  • @types/google-apps-script
  • typescript

Webpack 環境構築

必要なプラグイン

gas-webpack-plugin

fossamagna/gas-webpack-plugin: Webpack plugin for Google Apps Script

GAS の関数は global に登録されるようトップレベルで関数宣言をする必要がありますが、 Webpack で 1 ファイルにバンドルすると Webpack の関数スコープに閉じ込められてしまい global から参照出来ない形で出力されてしまいます。

gas-webpack-plugin はこれを解決する物で、コード中の global オブジェクトへの代入を検出して Webpack のバンドルファイルにトップレベルの関数宣言を注入してくれます。

es3ify-webpack-plugin

BryceHQ/es3ify-webpack-plugin: A simple webpack plugin to es3ify your code for old versions of ie, such as ie8.

GAS は ECMAScript の 5 とか 5.1 とかで書かないと動作しないみたいなんですが、以下のようにビルドツール、バンドラーを使って ES5 として出力されるコード自体の一部が動作しない場合があります。

exports.default = foo;

これは次のように書き直す事で GAS のエディタに怒られなくなります。

exports['default'] = foo;

しかし自動生成ファイルを手作業で修正するのはさすがに無いので、 es3ify を使って予約語まわりをいい感じにしてもらいます。

ちなみに es3ify-webpack-plugin の README には es3ify-loader 使えって書いてありますが、今回そっちは試さなかったのでここでは es3ify-webpack-plugin を前提に書いていきます。

設定ファイル

こんな感じになりました。 TypeScript のトランスパイルには @babel/preset-typescript を使っています。

webpack.config.js
const path = require('path');
const GasPlugin = require('gas-webpack-plugin');
const Es3ifyPlugin = require('es3ify-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  context: __dirname,
  entry: {
    main: path.resolve(__dirname, 'src', 'index.ts')
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js'
  },
  resolve: {
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      {
        test: /\.[tj]s$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [new GasPlugin(), new Es3ifyPlugin()]
};
.babel.rc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": ["ie 8"]
        }
      }
    ],
    "@babel/preset-typescript"
  ]
}
tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": [
      "es2015",
      "es2016",
      "es2017",
      "es2018"
    ],
    "allowJs": true,
    "declaration": false,
    "outDir": "./dist",
    "rootDir": "./src",
    "downlevelIteration": true,

    "strict": true,
    "noImplicitAny": true,

    "noUnusedLocals": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,

    "moduleResolution": "node",
    "esModuleInterop": true,

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["./src/**/*"],
  "exclude": [".git", "node_modules"]
}

tsconfig.json はまだ理解してない部分も多いから冗長かも。

環境構築は以上になります。

開発

これでモダンなフロントエンドと同じやり方で開発を進められるようになりましたが、エントリーポイントだけちょっと対応が必要になります。

GAS で登録したい関数を global オブジェクトのプロパティに代入していきます。
この時に型定義が無いかもなのですが、今回そこらへんは適当にやってしまいました。

src/index.ts
import { MyFunc } from "./my-func"

// プロパティが無いと言われるのを防ぐ程度の型定義
declare const global: {
  [x: string]: any ;
}

global.onOpen = function(e: any) {
  return MyFunc(e)
}

global.onInstall = function(e: any) {
  global.onOpen(e)
}

webpack コマンドでビルドし、 clasp push でデプロイ完了です。

watch

npm パッケージの watch を使うことで、 webpackwatch モードのように変更の度にビルドと clasp push を行えるようになります。

package.json
{
  "scripts": {
    "build": "node_modules/.bin/webpack",
    "watch": "node_modules/.bin/watch 'npm run build && clasp push' ./src"
  }
}

以上です。

23
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?