4
2

Google App Scriptを自分のマシンで開発できる google clasp を使ってみる・後編

Posted at

こちらの続きです。

  • claspは便利。だが自由度が高すぎて環境整えるのが大変

前回、まとめにこんなことを書きましたが、このデメリットを改善するための Google aside を使ってみます。

Google aside

Google aside (Apps Script in IDE)は Google 謹製の clasp 用テンプレートフレームワークです。実行後、いくつかの質問に回答することで、claspにちょうどいい開発環境を構築してくれます。

https://github.com/google/aside

asideの利用

aside はこのように使います。

$ asdf local nodejs 22.2.0
$ npx @google/aside init

実行すると、いくつか質問が出てきます。
この回答結果で clasp の開発に便利なコードが生成されます。

 Project Title:  calculate_sample
 Create an Angular UI?  No
 Generate package.json?  Yes
 Adding scripts...
 Saving package.json...
 Installing dependencies...
 Installing src template...
 Installing test template...
 Script ID (optional):  
 Script ID for production environment (optional):  
 Creating calculate_sample...

-> Google Sheets Link: https://drive.google.com/open?id=1rROKUn8XinG6puFg4wRT7CZ0FMEgOuoSo4U-CMtGX7Y
-> Apps Script Link: https://script.google.com/d/1iV_olJEdt5vMJ3UWdG_y-COeP5dFf6lPQjvYgN627Pfcd-vxtgsrR87G/edit

ただ、aside経由だとスタンドアローンスクリプトは作れず、必ず関連するスプレッドシートも作られます。

asideの実行により生成されたファイル・フォルダは以下の通りです。

yusuke@suzuki-m2-Mac-mini calculate_sample_aside % ls -al
total 832
drwxr-xr-x   25 yusuke  staff     800  7  1 21:50 .
drwxr-xr-x   12 yusuke  staff     384  7  1 21:47 ..
-rw-------    1 yusuke  staff     152  7  1 21:50 .clasp-dev.json
-rw-------    1 yusuke  staff     152  7  1 21:50 .clasp-prod.json
-rw-r--r--    1 yusuke  staff       1  7  1 21:50 .claspignore
-rw-r--r--    1 yusuke  staff     114  7  1 21:50 .editorconfig
-rw-r--r--    1 yusuke  staff      44  7  1 21:50 .eslintignore
-rw-r--r--    1 yusuke  staff     398  7  1 21:50 .eslintrc.json
-rw-r--r--    1 yusuke  staff      64  7  1 21:50 .gitignore
-rw-r--r--    1 yusuke  staff      19  7  1 21:50 .prettierignore
-rw-r--r--    1 yusuke  staff     125  7  1 21:50 .prettierrc.json
-rw-r--r--    1 yusuke  staff      14  7  1 21:47 .tool-versions
-rw-r--r--    1 yusuke  staff   11358  7  1 21:50 LICENSE
-rw-r--r--    1 yusuke  staff     124  7  1 21:50 appsscript.json
drwxr-xr-x    2 yusuke  staff      64  7  1 21:50 dist
-rw-r--r--    1 yusuke  staff     246  7  1 21:50 jest.config.json
-rw-r--r--    1 yusuke  staff     570  7  1 21:50 license-config.json
-rw-r--r--    1 yusuke  staff     553  7  1 21:50 license-header.txt
drwxr-xr-x  483 yusuke  staff   15456  7  1 21:50 node_modules
-rw-r--r--    1 yusuke  staff  342627  7  1 21:50 package-lock.json
-rw-r--r--    1 yusuke  staff    1528  7  1 21:50 package.json
-rw-r--r--    1 yusuke  staff    1229  7  1 21:50 rollup.config.mjs
drwxr-xr-x    4 yusuke  staff     128  7  1 21:50 src
drwxr-xr-x    3 yusuke  staff      96  7  1 21:50 test
-rw-r--r--    1 yusuke  staff     455  7  1 21:50 tsconfig.json

src以下にコードのサンプルが置いてあります。

  • src/index.ts
/**
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { hello } from './example-module';

console.log(hello());
  • src/example-modules.ts
/**
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
export function hello() {
  return 'Hello Apps Script!';
}

claspコマンドは使わず、npmコマンドを介して build を行います。npm run に指定できるコマンド、どんな処理を指定してるかは package.json の内容を確認してください。

% npm run build

> calculate-sample@0.0.0 build
> npm run clean && npm run bundle && ncp appsscript.json dist/appsscript.json

> calculate-sample@0.0.0 clean
> rimraf build dist

> calculate-sample@0.0.0 bundle
> rollup --no-treeshake -c rollup.config.mjs

src/index.ts  dist...
created dist in 612ms

ビルドすることでトランスコンパイルされ生成された dist/index.js が生成されます。このファイルをGASのランタイム環境に転送します。

  • dist/index.js
/**
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function hello() {
  return "Hello Apps Script!";
}

console.log(hello());

dist以下のこれをGASに転送するため、deployを行います。

lint, testを実行し、dist/index.js, dist/appsscript.json を転送。

% npm run deploy

> calculate-sample@0.0.0 deploy
> npm run lint && npm run test && npm run build && ncp .clasp-dev.json .clasp.json && clasp push -f

> calculate-sample@0.0.0 lint
> npm run license && eslint --fix --no-error-on-unmatched-pattern src/ test/

> calculate-sample@0.0.0 license
> license-check-and-add add -f license-config.json

No default format specified. Using {"prepend":"/*","append":"*/"} as backup
! Inserted license into 1 file(s)
Command succeeded
=============

WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <5.2.0

YOUR TYPESCRIPT VERSION: 5.5.2

Please only submit bug reports when using the officially supported version.

=============
(node:44029) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)

> calculate-sample@0.0.0 test
> jest test/ --passWithNoTests --detectOpenHandles

 PASS  test/example-module.test.ts
  example-module
    hello
       Returns a hello message (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.815 s
Ran all test suites matching /test\//i.

> calculate-sample@0.0.0 build
> npm run clean && npm run bundle && ncp appsscript.json dist/appsscript.json

> calculate-sample@0.0.0 clean
> rimraf build dist

> calculate-sample@0.0.0 bundle
> rollup --no-treeshake -c rollup.config.mjs

src/index.ts  dist...
created dist in 491ms
(node:44296) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
└─ dist/appsscript.json
└─ dist/index.js
Pushed 2 files.

前回claspで作ったTypeScriptのコードをasideに移植

ということで、前回 clasp で作った計算プログラムを aside のプロジェクトに移植してみます。

  • src/Logger.ts
// Logger.ts

export function logMessage(message: string): void {
  Logger.log(message);
}
  • src/Utilitiles.ts
// Utilities.ts

export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}
  • src/index.ts
/* eslint-disable @typescript-eslint/no-unused-vars */
import { logMessage } from './Logger';
import { add, subtract } from './Utilities';

function main() {
  const resultAdd = add(5, 3);
  const resultSubtract = subtract(5, 3);
  //const resultSubtract2 = add("aaa", "bbb");

  logMessage(`Addition Result: ${resultAdd}`);
  logMessage(`Subtraction Result: ${resultSubtract}`);
  //logMessage(`${resultSubtract2}`);
}

ついでにテストも書いちゃう

test/utilities.test.ts ファイルを作って、src/Utilities.ts のユニットテストを書いてみる。

テストツールは Jest が使われます。

ユニットテスト。GASでスプレッドシートを直に操作する等のUI処理は、テストが書きづらいので書きません。今回は計算や配列操作、データ処理を行うロジックのコードについて、ユニットテストが書いています。

  • test/utilities.test.ts
import { add, subtract } from '../src/Utilities';

describe('utilities-test', () => {
  // Path: test/utilities.test.ts
  // add function test
  describe('add', () => {
    it('add 2 number 1 (integer number)', () => {
      expect(add(2, 3)).toBe(5);
      expect(add(-2, 3)).toBe(1);
      expect(add(2, -3)).toBe(-1);
      expect(add(-2, -3)).toBe(-5);
    });

    it('add 2 number 2 (decimal number)', () => {
      expect(add(2.4, 3.5)).toBe(5.9);
      expect(add(-2.4, 3.5)).toBe(1.1);
      expect(add(2.4, -3.5)).toBe(-1.1);
      expect(add(-2.4, -3.5)).toBe(-5.9);
    });

    it('add 2 number 3 (NaN)', () => {
      expect(add(NaN, 3.5)).toBe(NaN);
      expect(add(1, NaN)).toBe(NaN);
      expect(add(NaN, NaN)).toBe(NaN);
    });
  });

  // Path: test/utilities.test.ts
  // subtract function test
  describe('subtract', () => {
    it('subtract 2 number 1 (integer number)', () => {
      expect(subtract(2, 3)).toBe(-1);
      expect(subtract(-2, 3)).toBe(-5);
      expect(subtract(2, -3)).toBe(5);
      expect(subtract(-2, -3)).toBe(1);
    });

    it('subtract 2 number 2 (decimal number)', () => {
      expect(subtract(2.4, 3.5)).toBe(-1.1);
      expect(subtract(-2.4, 3.5)).toBe(-5.9);
      expect(subtract(2.4, -3.5)).toBe(5.9);
      expect(subtract(-2.4, -3.5)).toBe(1.1);
    });

    it('subtract 2 number 3 (NaN)', () => {
      expect(subtract(NaN, 3.5)).toBe(NaN);
      expect(subtract(1, NaN)).toBe(NaN);
      expect(subtract(NaN, NaN)).toBe(NaN);
    });
  });

});

テストの実行。こんな感じで通りました。

% npm run test

> calculate-sample@0.0.0 test
> jest test/ --passWithNoTests --detectOpenHandles

 PASS  test/utilities.test.ts
 PASS  test/example-module.test.ts

Test Suites: 2 passed, 2 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        0.546 s, estimated 1 s
Ran all test suites matching /test\//i.

ビルド

テストも通ったので、npm run build でコードをビルド。

こんなコードが生成されます。

  • dist/index.js
/**
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function logMessage(message) {
  Logger.log(message);
}

function add(a, b) {
  return a + b;
}
function subtract(a, b) {
  return a - b;
}

function main() {
  const resultAdd = add(5, 3);
  const resultSubtract = subtract(5, 3);
  logMessage(`Addition Result: ${resultAdd}`);
  logMessage(`Subtraction Result: ${resultSubtract}`);
}

あえてバグを作ってみると…

ちなみに、文字列の足し算を行えるようにしてclasp版でGASにpushされてしまった件。

function main() {
  const resultAdd = add(5, 3);
  const resultSubtract = subtract(5, 3);
  const resultSubtract2 = add("aaa", "bbb");

  logMessage(`Addition Result: ${resultAdd}`);
  logMessage(`Subtraction Result: ${resultSubtract}`);
  logMessage(`${resultSubtract2}`);
}

これを、aside版で行ってみると、

% npm run build

> calculate-sample@0.0.0 build
> npm run clean && npm run bundle && ncp appsscript.json dist/appsscript.json

> calculate-sample@0.0.0 clean
> rimraf build dist

> calculate-sample@0.0.0 bundle
> rollup --no-treeshake -c rollup.config.mjs

src/index.ts  dist...
[!] (plugin rpt2) RollupError: [plugin rpt2] src/index.ts:6:31 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

6   const resultSubtract2 = add("aaa", "bbb");
                                ~~~~~

src/index.ts

    at getRollupError (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup/dist/shared/parseAst.js:282:41)
    at Object.error (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup/dist/shared/parseAst.js:278:42)
    at Object.error (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup/dist/shared/rollup.js:804:32)
    at RollupContext.error (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup-plugin-typescript2/src/context.ts:35:17)
    at /Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup-plugin-typescript2/src/diagnostics.ts:70:17
    at Array.forEach (<anonymous>)
    at printDiagnostics (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup-plugin-typescript2/src/diagnostics.ts:42:14)
    at typecheckFile (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup-plugin-typescript2/src/index.ts:67:3)
    at Object.<anonymous> (/Users/yusuke/projects/MIERUNE/gas/calculate_sample_aside/node_modules/rollup-plugin-typescript2/src/index.ts:269:5)
    at Generator.next (<anonymous>)

このように、npm のビルド時点で怒られ、デプロイができなくなります。ということで、claspのように、コードが間違っていても push されることはありません。

まとめ

  • 素のGASを書くのは色々ツライが、claspを使うとメリットが色々享受できる
  • claspは便利だが自由度が高すぎ
  • Google謹製のasideはclaspを制御するのに都合がよい

以上

4
2
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
4
2