nagashigaki

フロントエンドや釣りで気になったことを記していきます。

Google App Scriptをモダンな開発っぽくする環境を作ってみた(ES6, browserify, eslint, mocha)

f:id:takuyantakuyan:20170408135601p:plain
お前は今更何を言ってるんだ?というテーマですが整理するために。
Google App Script(以下GAS)とはjavascriptでGoogle App(今ではG suite?)の豊富な機能を堪能でき、ある程度の作業を自動化することができます。しかも無料で!指定時間に勝手にスクリプトを起動することもできちゃいます。
例えば、複数のcsvをDriveにアップしてボタンをポチっとするだけで目的のデータが揃い、そのデータをメールで飛ばしたりできるのは非常にありがたいです。

導入する環境

このGASの機能をもっと使いこなしたい。具体的には

  • npmで管理
  • es6
  • gulp
  • browserifyでモジュール化
  • eslintでコードチェック
  • Mocha&Chaiでテスト

環境を整えるにあたって以下のサイトを参考にさせて頂きました。この記事がなかったら途中で挫折してしまったかも・・・
tech.speee.jp

上記参考サイトはDriveからGoogle Apps Scriptを新規作成するStandaloneタイプのスクリプトですが、今回は
「直感的にSpreadSheetから簡単に操作したい(*´∀`*)」
ということで、onOpenなどのトリガーが使えるSpreadSheetに紐付いたスクリプト(Container Bound Script)で作成します。つまりは、SpreadSheetのメニューのスクリプトエディタで起動させるスクリプトです。
この辺りはqiita.comに詳しく解説されてます。
参考サイトのようにwatchして自動的にスクリプトをアップロードするというスマートな方法はできず、コンパイルしてペタペタとスクリプトエディタに貼り付ける開発ですね。めんどくさいですが、いつも保存するたんびにコンパイルをしてるわけでもないので目をつむります。
では前提条件が整ったところで、早速環境を作っていきましょう!(*´ω`*)

環境を整える

f:id:takuyantakuyan:20170408140330p:plain
windowsなら日本語のパスがないようにCの中に開発ファイル作るのがおすすめです。node.jsベースになるので、入れてない方はまずnode.jsをインストール。

npmとgitの初期化
$ npm init
$ git init

gitも使うので今のうちに初期化

babel
$ npm install --save-dev babel babel-core babel-preset-es2015 babel-register 

gulpfileなど設定ファイルもES6にしたいのでpackage.jsonに

"babel": {
  "presets": [
    "es2015"
  ]
}

と記入。

gulpまわり
$ npm install --save-dev gulp gulp-babel gulp-exec gulp-plumber

globalにgulpを入れてない人は、globalにもインストール。使い慣れてる人はお好みのプラグインいれてください!

browserify

とりあえずbrowserifyとそれを使うためのvinylをインストール。恥ずかしながら、いつもwebpackだったのでbrowserifyはじめてさわった(^_^;)

$ npm install --save-dev browserify vinyl-source-stream

GAS用のbrowserifyプラグイン、gasifyがあるのでそれもインストール。こいつのおかげで、結合したファイルでもスクリプトエディタ上で起動する関数が認識されます。

$ npm install --save-dev gasify

つぎにbabelifyを導入するのですがここに落とし穴があります。現状最新のbabelifyではエラーが発生してしまうことがあります。自分の環境でもエラーが発生しました。ということで、今回はbabelify5系を指定してインストールします。

$ npm install --save-dev babelify@5.0.5
テストツール

テストフレームワークはmocha、アサーションライブラリはchaiを使います。

$ npm install --save-dev mocha chai gulp-mocha
eslint

最初の状態だと SpreadsheetAppなどを使ったときに
「こんなの定義されてないよヽ(`Д´)ノプンプン」
と怒られるので、GAS用のeslintプラグインのeslint-plugin-googleappsscriptもインストール。

$ npm install eslint eslint-plugin-googleappsscript

.eslintrcはpluginの利用と、globalも利用するのでだいたい以下のような感じになります。また、Atomを使っているのでgulpにeslintをチェックせず、コーディング中に確認します。

{
  "plugins": [
    "googleappsscript"
  ],
  "env": {
    "googleappsscript/googleappsscript": true,
    "es6": true,
    "mocha": true
  },
  "globals": {
        "global": true
    },
  "parserOptions": {
      "ecmaVersion": 6, 
      "sourceType": "module"
    },
  "rules": {
   //ルール
  }
}

適宜.eslintignoreにeslintを適用しないものを追記してください。

ファイル構成
┌─ dest コンパイル先
├─ src 開発フォルダ
└─ test test用フォルダ

で開発してますので、それに合わせます。

gulpfileの作成

ES6で書きたいので、gulpfile.babel.jsを

import gulp from 'gulp';
import browserify from 'browserify';
import source from 'vinyl-source-stream';
import mocha from 'gulp-mocha';

const compileFile = 'main.js';

gulp.task('dest', () => {
  browserify({
    entries: ['src/'+compileFile]
  })
  .transform('babelify')
  .plugin('gasify')
  .bundle()
  .pipe(source(compileFile))
  .pipe(gulp.dest('dist'));
});

gulp.task('test', () => {
  gulp.src('test/**/*.js', {read: false})
      .pipe(mocha({
        reporter: 'spec',
        compilers: 'js:babel-core/register'
      }));
});

gulp.task('default', ['test', 'dest'], () => {});

最小限これだけで大丈夫です!!

お疲れさまでした

これで環境は整えました。ちなみに最終的なpackage.jsonは

{
  "name": "gas",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel": "^6.23.0",
    "babel-core": "^6.24.0",
    "babel-preset-es2015": "^6.24.0",
    "babel-register": "^6.24.0",
    "babelify": "^5.0.5",
    "browserify": "^14.3.0",
    "chai": "^3.5.0",
    "gasify": "^0.1.0",
    "gulp": "^3.9.1",
    "gulp-babel": "^6.1.2",
    "gulp-exec": "^2.1.3",
    "gulp-mocha": "^4.3.0",
    "gulp-plumber": "^1.1.0",
    "mocha": "^3.2.0",
    "vinyl-source-stream": "^1.1.0"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  }
}

なので、足りない部分はコピペしてインストールしてください。

開発する

簡単なスクリプトを書いてみます。

src/main.js

import hello from './hello';

global.main = () => {
  const name = SpreadsheetApp.getActive().getName();
  Logger.log(hello(name));
};

global.xxx にすることでスクリプトエディタがxxxを認識し起動させることができます。

src/hello.js

export default function hello(name) {
  return 'hello '+name+'!';
}

test/helloTest.js

import {assert} from 'chai';
import hello from '../src/hello';

describe('hello', () => {
  it('should get hello world! when name is world', () => {
    assert.equal(hello('world'), 'hello world!');
  });
});

これでgulpとコマンドをうつと、テストを通してスクリプトエディタに貼り付ける用のスクリプトを吐き出します。
うまくできましたか??
貼り付けると、スクリプトエディタで保存すると関数を自動認識するので、それを起動してください!

雑談

最近、GASのエンジンが

  • const
  • let

をサポートしたのを知りました。でもObject.assignはサポートされてない・・・
対応リストがほしい・・・

Paizaのjavascriptのコーディングテストで問題を解く前にメンタル的に失格になった話

フロントエンドの募集ってないのかなあと思ってウロウロしてたところ、ITプログラマー・エンジニア転職のpaizaに行き着きました。
このサイトの説明は省略するとして、応募するときにコーディングテストをして、面接にもそれがれっきとした資料として採用されるらしいです。

f:id:takuyantakuyan:20170117012329j:plain

気軽に応募してくださいという募集のテストを受けました。
言語はどれでもOKということなので

「最近やっとrubyを勉強して少しならわかるようになったんだけど、フロントエンジニア募集ならjavascript一択でしょ!どんな問題がでるのかな?(ドキドキ)」

と気軽な気持ちでやったら痛い目をみました(泣)

問題:標準入力を出力するようにしましょう!

えっと・・・標準入力ですか・・・そんなのがjavascriptちゃんにあったの知らないです・・・( ゚д゚)ポカーン
とすること数分。

「あれ、実は基本中の基本で僕だけが知らないのでは!?そんなレベルで、javascript触れますよ~なんて言ってたと周りにバレたら赤っ恥だ!Σ(゚Д゚)」

すぐにgoogle先生にカンニングしました。おそらくnode.js周りだろうなと思ったらやっぱりそうですよねw
解答としては、変数を console.log()の引数に入れるだけでしたが、実力としてはスタートラインにすら立てませんでした。

javascriptを触れると言ったら、Node.jsを触れると思われている?

カンニングした後
「これnode.jsやん。まあjavascriptには違わないけど、node.jsはnode.jsって分類してよ~ん」
とモヤモヤしたが冷静になると、
もはや市場では「javascriptを組める人」=「node.jsを組める人」になってるのでは!?と思いました。
確かにフロントエンドエンジニアであったら、多少なりともバックグラウンドの基礎知識は必要になるのは重々承知でした。
rubyを最低限くらい組めるようにならないとなと勉強しはじめましたがnode.jsも勉強もする必要があるのかな・・・
もしかすると例えば
「es6ってスゲー便利だよー」
「Reactをフロントエンドで使ったサイトを構築したよー」
ってのは当然として
「node.jsも組めるよー」
という人だけが活躍する時代が来るのかもしれない、と考えてみたりしました。

Atomで開く : Open With Atom が表示されない問題を解決した

f:id:takuyantakuyan:20170102021422p:plain
年末ということで、PCも掃除することにしました。
で以前から調子が悪かった愛するAtomを一旦消去し、再インストールすることに。
すると「Open With Atom」が消えてるΣ(゚Д゚)
作業をするときは、これでプロジェクトを開いていたので、これがないと仕事始めから躓いてしまう・・・

google先生に解決策を聞いてみた

レジストリを弄って調整する方法。

実は前回に、「Open With Atom」の表示とその横に正常にAtomのアイコンを表示するためにこの方法を参考にさせて頂いた。
確かに、目的の動作をしたのだが、副作用としてAtomが少し調子が悪くなった(アップデートなどがうまくいかない)
なので、今回はこれ以外の方法を探してみる。

実は設定項目一発でできた

というわけで、いろいろ探ってみるとAtomの中の設定項目にそれらしきオプションがあることが発覚!
そのオプションを適用すると、元通りの「Open With Atom」が表示されました!!

やり方
  1. Atomを起動し、メニューから"File"→"setting"で設定画面を表示
  2. その中の"System"を選択。f:id:takuyantakuyan:20170102005615p:plain
  3. OSとの連携(ファイルの開き方系)の設定項目にチェックをつける
    1. windowsでいう、プログラムから開くにAtomの項目を追加する
    2. フォルダのメニュー(context menu)で「Open With Atom」を追加する
    3. ファイルのメニュー(context menu)で「Open With Atom」を追加する

僕の場合、とりあえず全部チェックをつけました。すると、Open With Atomが復活しました。

あれ・・・?

Open With Atomが復活し、めでたしめでたしということだったのですが
チェックをいれた項目が
f:id:takuyantakuyan:20170102005615p:plain
のままでした。
でも、Open With Atomはきちんと動いてるしバグなのかな・・・

babel+es6 でmixpanelのtrackが動かなくなった話

f:id:takuyantakuyan:20161214092544p:plain
babel+es6の環境で開発していて、最後にイベントにmixpanelのトラッカーを設置して終わりだなε-(´∀`*)ホッ
となったときに、コンパイルしてテストするとトラッカーが動かず、ドキュメントをあさってもなかったのでその対処方法。

問題が起こった場面

mixpanel.com
ここのコードを</head>直前に貼り付け

html

<button id="testBtn">test!!</button>

js

class Tester {
  test(){
    mixpanel.track('test1', {type: 'test1'});//送れる!!
    document.getElementById('testBtn').addEventListener('click', (e) => {
      mixpanel.track('test2', {type: 'test2'});//送れない!!
      console.log('foo');
    });
  }
}

const tester = new Tester();
tester.test();

このときtest1は無事送信されますが、イベントリスナーに登録したtest2が送信されませんでした。
状況を整理すると

  1. mixpanelは読み込まれてる
  2. EventListenerの中だけtrackできない
  3. クリックイベントはきちんと発火する(コンソールにfooが表示)

おそらくthis系でエラーが起こってる感じかな??
ちなみにwebpack、browserifyで同様のエラーがおこるみたい。

es6でmixpanelを動かしたい!!

誰かライブラリ作ってくれていないかな?と思ったらやっぱりありました!
GitHub - mixpanel/mixpanel-js: Official Mixpanel JavaScript Client Library
使い方はインストールして、普通のmixpanelと同じように使うだけでOK!webpack、browserifyどちらでも大丈夫です!

1 npmからインストール
npm install --save-dev mixpanel-browser

ライブラリの名前はmixpanel-jsなんだけど、nodeのと区別するためにbrowserにしてるみたいです。

2 mixpanelをimportまたはrequire
import mixpanel from 'mixpanel-browser';
//var mixpanel = require('mixpanel-browser');
3 初期設定
mixpanel.init('プロジェクトのTOKEN');

これで上の動かなかったコードでもきちんとイベントが発火されたら、送信されました!
ちなみに、他のモジュールでも使うならこんな感じかな?

import mixpanel from 'mixpanel-browser';

class Tester {
  constructor(){
    mixpanel.init('mixpanel token');
  }

  test(){
    mixpanel.track('test1', {type: 'test1'});
    document.getElementById('testBtn').addEventListener('click', (e) => {
      mixpanel.track('test2', {type: 'test2'});
      console.log('foo');
    });
  }
}
const tester = new Tester();
export default tester;
tester.test();

mixpanelのデバッグモード

この問題でいろいろドキュメントをあさってると、デバッグモードなるものを発見!
mixpanel.commixpanel.init()の第2引数に{debug: true}を入れます。
すると、コンソールにmixpanelの情報、trackが送った内容を確認することができます!これで、mixpanelの管理画面から逐一送信された値を確認せずに、コンソールから確認できますね!

webpackでCDNから取ってきたりした外部のjQueryなどを利用する方法

f:id:takuyantakuyan:20161201094313j:plain
es6でモジュール化にするときは人気のwebpackを利用するケースが多くなってきました。(むしろこれが当たり前になってきましたw)
そこで、既にテンプレートが完成した状況でそこそこのボリュームのjsを書くことになりました。(例えば、wordpress中に個別ページにjsを使ったアプリを作る場合ですね)
既にCDNからjQueryをインストールされ、jQuryありきのコーディングだったのでこれを削除せずにwebpack内で、この外部のjQueryを利用したいと思いました。

webpackに外部のjQueryを使うように設定する

jQueryをCDNから使います

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

webpack.config.js内に次のプロパティを追加して下さい。

externals: {
  jquery: 'jQuery'
},

これで、ビルドしたjsは外部の(グローバルのと言っていいのかな?)jQueryを引っ張ってきます。
使い方は、npm でインストールしたときと同様に

import jQuery from 'jquery';
//$を使いたい場合は↓で
import $ from 'jquery';

とすればOK!

毎回importするのめんどくさい!!となればwebpack.config.jsに

const webpack = require('webpack');

module.exports = {
  /*中略*/
  plugins: [
    new webpack.ProvidePlugin(
      {
        jQuery: "jquery",
        $: "jquery",
      }
    ),
  ],
};

これでimportしなくても使えます!