16
18

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 3 years have passed since last update.

Google Apps Script の開発環境構築方法

Posted at

はじめに

Google Apps Scriptを開発するときにclaspを導入してソース管理し始めた後、もっと色々できないかと調べた結果を備忘録として記載します。

GOAL

開発環境として最終的に下記を利用して開発できるようになります。

  • VSCode: コードエディター
  • clasp: Google Apps Scriptをローカルで開発できるようにするもの
  • TypeScript: プレーンJavaScriptにコンパイルされるJavaScriptの型付きスーパーセット
  • ESLint: JavaScriptコードの問題を見つけて修正することができる
  • Jest: シンプルさを重視した楽しいJavaScriptテストフレームワーク

claspのインストール〜プロジェクト作成

https://github.com/google/clasp
上記ページのInstallセクションの記載通りに実行します。

$ npm install -g @google/clasp

ログインします。初回だとブラウザが起動してclaspへのアクセス許可をもとめられるので許可すると~/.clasprc.jsonに認証情報が保存されます。

$ clasp login

プロジェクトを作成します。今回は、standaloneプロジェクトを作成します。

$ mkdir gas-example
$ cd gas-example
$ clasp create --type standalone
Created new standalone script: https://script.google.com/d/xxxxxxxxxx/edit
Warning: files in subfolder are not accounted for unless you set a '.claspignore' file.
Cloned 1 file.
└─ appsscript.json

Typescriptのインストール

最初にpackage.jsonを作成します。色々質問されますが、とりあえず、全部EnterでOK。

$ npm init

typescript関連のパッケージをインストールします。

$ npm install --save-dev typescript ts-node @types/node

https://github.com/google/clasp/blob/master/docs/typescript.md
上記ページのPrerequisitesセクションの記載通りに実行します。

$ npm i -S @types/google-apps-script

tsconfig.jsonというファイルを作成して、TypeScript機能を有効にします。
ソースコードは、src/に配置し、テストコードはtest/に配置することにします。

tsconfig.json
{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true
  },
  "include": [
    "src/**/*",
    "test/**/*"
  ]
}

.clasp.jsonも下記の通りに修正します。

.clasp.json
{
    "scriptId":"xxxxxxxxx",
    "rootDir": "src/",
    "fileExtension": "ts"
}

appsscript.jsonファイルもsrc/に移動します。

$ mkdir src test
$ mv appsscript.json src/

ESLint & Prettierのインストール

https://eslint.org/docs/user-guide/getting-started
上記ページのInstallation and Usageセクションの記載通りに実行します。

$ npm install eslint --save-dev

次に.eslintrc.jsonをセットアップします。

$ npx eslint --init
? How would you like to use ESLint? 
To check syntax, find problems, and enforce code style

? What type of modules does your project use? 
None of these

? Which framework does your project use? 
None of these

? Does your project use TypeScript? 
Yes

? Where does your code run? 
Node

? How would you like to define a style for your project? 
Use a popular style guide

? Which style guide do you want to follow? 
Standard: https://github.com/standard/standard

? What format do you want your config file to be in? 
JSON
Checking peerDependencies of eslint-config-standard@latest
The config that you've selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest eslint-config-standard@latest eslint@>=6.2.2 eslint-plugin-import@>=2.18.0 eslint-plugin-node@>=9.1.0 eslint-plugin-promise@>=4.2.1 eslint-plugin-standard@>=4.0.0 @typescript-eslint/parser@latest

? Would you like to install them now with npm? 
Yes

Installing @typescript-eslint/eslint-plugin@latest, eslint-config-standard@latest, eslint@>=6.2.2, eslint-plugin-import@>=2.18.0, eslint-plugin-node@>=9.1.0, eslint-plugin-promise@>=4.2.1, eslint-plugin-standard@>=4.0.0, @typescript-eslint/parser@latest

https://prettier.io/docs/en/install.html
https://github.com/prettier/prettier-eslint
https://github.com/prettier/eslint-config-prettier
https://github.com/prettier/eslint-plugin-prettier
上記ページを参考にしながらprettier関連パッケージをインストールします。

$ npm install --save-dev --save-exact prettier
$ npm install --save-dev prettier-eslint eslint-config-prettier eslint-plugin-prettier

.eslintrc.jsonのextendsの部分を修正します。

.eslintrc.json
{
    "env": {
        "es6": true,
        "node": true
    },
    "extends": [
        "standard",
        "eslint:recommended",
        "plugin:@typescript-eslint/eslint-recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:prettier/recommended",
        "prettier/@typescript-eslint"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 2018
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
    }
}

Jest & ts-jestのインストール

https://jestjs.io/docs/ja/getting-started
上記ページのGetting Startedセクションの記載通りに実行します。

$ npm install --save-dev jest

https://github.com/kulshekhar/ts-jest
上記ページのGetting Started - Installingセクションを参考にし、下記を実行します。

$ npm i -D ts-jest @types/jest

次にjest.config.jsファイルを生成します。

$ npx ts-jest config:init

hello.ts & hello.test.tsの作成

https://github.com/google/clasp/blob/master/docs/typescript.md
hello.tsをベースにsrc/hello.tstest/hello.test.tsを作成します。

src/hello.ts
const greeter = (person: string): string => {
  return `Hello, ${person}!`;
};
function testGreeter(): string {
  const user = "Grant";
  const msg = greeter(user);
  Logger.log(msg);
  return msg;
}
export { greeter, testGreeter };
test/hello.test.ts
import { greeter, testGreeter } from "../src/hello";
describe("test.ts test", () => {
  beforeAll(() => {
    Logger.log = jest.fn().mockImplementation(msg => {
      return console.log(msg);
    });
    jest.spyOn(Logger, "log");
  });
  test("greeter", () => {
    const person = "World";
    const expected = greeter(person);
    expect(expected).toBe("Hello, World!");
  });
  test("testGreeter", () => {
    const expected = testGreeter();
    expect(Logger.log).toBeCalled();
    expect(expected).toBe("Hello, Grant!");
  });
});

GoogleAppScriptで定義されているLogger.logをjestでモック化するためにjest.config.jsを下記の通り修正します。

jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  globals: {
    Logger: {}
  }
};

テストコードの実行

下記コマンドでテストコードが実行できます。
Logger.logの代わりにconsole.logが実行されていることがわかります。

$ npx jest
 PASS  test/hello.test.ts
  test.ts test
    ✓ greeter (2ms)
    ✓ testGreeter (8ms)

  console.log test/hello.test.ts:6
    Hello, Grant!

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.821s
Ran all test suites.

console.logを出力したくない場合

$ npx jest --silent=true
 PASS  test/hello.test.ts

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.991s, estimated 3s

カバレッジを出力したい場合

$ npx jest --silent=true --coverage
 PASS  test/hello.test.ts
----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 hello.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.7s

Apps Scriptを実行する

typescriptファイルをapps scriptサーバにpushします。

$ clasp push
└─ src/appsscript.json
└─ src/hello.ts
Pushed 2 files.

script.google.comでApps Scriptプロジェクトを開きます。

$ clasp open
Opening script: https://script.google.com/d/xxxxxxxxxx/edit

ブラウザが開くのでtestGreeter()を実行し、[表示]> [ログ]を押してログを表示することで、結果を確認できます。

hello.tsは、下記のようにGoogle Apps Scirptに変換されていることがわかります。

hello.gs
// Compiled using ts2gas 3.4.4 (TypeScript 3.7.2)
var exports = exports || {};
var module = module || { exports: exports };
var greeter = function (person) {
    return "Hello, " + person + "!";
};
exports.greeter = greeter;
function testGreeter() {
    var user = "Grant";
    var msg = greeter(user);
    Logger.log(msg);
    return msg;
}
exports.testGreeter = testGreeter;

おまけ:VSCodeの設定

ファイルセーブ時に自動的にESLintで自動整形できるように設定する。

.vscode/settings.json
{
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
}

デバッグ実行の設定

.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "args": [
                "${relativeFile}"
            ],
            "runtimeArgs": [
                "--nolazy",
                "-r",
                "ts-node/register"
            ],
            "sourceMaps": true,
            "cwd": "${workspaceRoot}",
            "protocol": "inspector"
        },
        {
            "type": "node",
            "request": "launch",
            "name": "Debug Tests",
            "args": [
                "${relativeFile}"
            ],
            "runtimeArgs": [
                "--inspect-brk",
                "${workspaceRoot}/node_modules/jest/bin/jest.js",
                "--runInBand",
                "--silent=true",
                "-o"
            ],
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen",
            "port": 9229
        }
    ]
}

#おまけ2:package.jsonの設定
package.jsonを下記のように修正することでnpm runで実行できるようになります。

package.json
〜省略〜
  "scripts": {
    "ts-node": "npx ts-node",
    "test": "npx jest --silent=true --coverage",
    "test-only": "npx jest --silent=true -o"
  },
〜省略〜

テスト実行

$ npm run test

> gas-example@1.0.0 test /Users/xxxxx/gas-example
> npx jest --silent=true --coverage

 PASS  test/hello.test.ts
----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 hello.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.227s
16
18
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
16
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?