トップ 最新 追記

2017-06-19 [長年日記]

_ Google Apps Script開発にstaging環境を用意してContinuous Deployment

背景

  • Google Apps Script 開発もモダンになっており、Git + GitHub + CI/CD な構成は実現できることが分かった
  • でもやはり master ブランチ以外は staging 環境の方へ deploy したい
  • しかし gas-manager などによる Google Drive API を利用した deploy の際には、deploy 先となるプロジェクトの Drive ID が必要になるので、Drive ID の切り替えが必要。しかもこの ID などの情報は JSON ファイル で保存されているのが前提

対処方法の基本的な考え方

  • deploy 前に (gas-managerで言う)gas-project.json や必要となる Spreadsheet の ID を差し替える仕組みを動かす ( preprocess )
  • 実行時に必要なパラメータは実行時にスイッチする仕組みを入れてもよいが、どうせ書き換えが必要な ID があるし、実行時の if スイッチが増えるのはバグが増えるのと同義なので、build 時に書き換えができるようにすればよい

具体的な方法

  1. パラメータの詰まった JSON を用意する
  2. gas-project.json などはこの 1 の JSON と template をもとに upload 前に生成する
  3. Browserify で実行コードを生成する際に、1 で用意した JSON から必要な環境の分だけを抽出した JSON を生成し、これを require する

これで実行時に必要なパラメータは実行時にコードの中に詰まっている状態になる。秘密情報でなければこの方法で埋め込んでしまうのがリーズナブルな解決方法だと思う。

gas-project.json の template は以下のような感じ。

gas-project.json.mjs

{
  "src": {
    "fileId": "{{fileId}}",
    "files": {
      "<scriptname>": {
        "path": "src/code.js",
        "type": "server_js"
      }
    }
  }
}

パラメータの詰まった JSON はこんな感じ。

gas-components.json

{
  "production": {
    "fileId": "",
    "targetBook": ""
  },
  "staging": {
    "fileId": "",
    "targetBook": ""
  }
}

全体像を図にすると以下のような感じ。

GASプロジェクトにpreprocessを入れてstaging環境への切り替えを実現する


2017-06-23 [長年日記]

_ Browserify x Babelでproductionコードを作るために分かっていなかったこと

ようやく本格的に Node.js + ES2015 ベースで JS がそこそこ快適に書けるようになってきました。

が、FAQ はちゃんと読もう!

babel/babelify: Browserify transform for Babel

思いっきり

Why aren't files in node_modules being transformed?

This is the default browserify behavior.

って書いてある!

結論

  • browserify transform は指定した順番に適用されるので、babelify と組み合わせている場合、まず babelify を通さないと例えば import などでいきなりコケる
  • brfs は babel と相性がよくないので brfs-babel を使う
  • とにかく browserify -g babelify -t babelify しろ

前提

Node.js でテストコードを書き、テストを高速に回すために適宜 Sinon などで stub を使う比較的モダンな開発が分かっていることとする。

課題

  • 依存する npm package のコードをすべて把握するのはなかなか難しい
  • 普段のテストは Node.js で動かすが production コードのターゲット環境が Node.js でない(例えばブラウザ)場合は、当たり前だが最終的にターゲット上でどう動くかは完全には分からない

対策

ブラウザなど Browserify を使うことで Node.js で開発したコードをターゲット上で動かすことができる場合、

  • browserify -g babelify -t babelify を指定することで依存パッケージまるごと Babel を通すことができる。これだけでも依存パッケージの組み合わせで悩むことはだいぶ減るはず(-g は global transform の意味)
  • eslint を使う
    • ブラウザなら eslint-plugin-compat を、Google Apps Script (で DI しないなら)eslint-plugin-googleappsscript 辺りを追加

辺りが有効な対策になりそう。

実際に困ったこと

Browserify は基本的には Node.js, CommonJS の世界のライブラリをブラウザの世界に連れてくることが仕事である。ただし、エンドポイントとなるコードから呼び出されているコードすべてに対してあらゆる transform を適用することはデフォルトの動作となっていない。確かに、IO にタッチせずロジックや加工を担う、Node.js で通常通り動くライブラリがすでにあって、これをブラウザの世界に連れてくる、というだけなら require の部分さえ解決すればだいたいはうまくいく。現実的な選択と言ってよいだろう。

これは言い換えると

  • require 対象が ES2015
  • ターゲット環境が ES5

な組み合わせで、browserify -t babelify を利用して ES5 ready なコードを生成したい場合、デフォルトでは build 済みのコードはすべてが ES 5 ready になるわけではない。

自分で書いている ES2015 なメインのコードは ES5 ready になってくれるだろう。ただし、require 先の ES2015 ready なコードは、 require の部分が解決されただけで bundle され、結果として ES2015 ready なコードが混ざったものができあがってしまう。

これを ES2015 ready な環境(例えば Node.js や最新のブラウザ)でテストしても問題は発見できない。あくまで ES 5 な環境でテストする必要がある。

とは言え、必ず ES5 環境で動かしてテストしなければいけないとなるとテストの実行コストが大きくなってしまう。ということで現実的な選択肢としては eslint で正しく ES5 互換のコードが生成されたかどうかを確認する、という形でだいぶマシになるだろう。

参考


2017-06-24 [長年日記]

_ ~/.netrcの使えないCI上からGitHubなどのprivate repositoryをprivate package代わりに使う

前提

  • deploy 先で build プロセスを動かすことのできないプロジェクトであること
  • ~/.netrc が無効な git クライアントが動いている

※ 今回の方法は依存パッケージを記述するファイルを強引に書き換えてしまうので、Gemfile と Gemfile.lock が揃っていないとそもそも build できない Heroku x Rails のような環境では使えないはずです。試してないけど。

方法

  • 環境変数に token を持つ
  • package を install する前に依存先の URI を token 付きのものに書き換える
  • yarn を使っている場合は yarn install --no-lockfile オプションを使う

コード

以下のようなコードを事前準備の段階で実行すれば実現できる。

#! /bin/sh

if [ -n "$CIRCLE_SHA1" -a -n "$GITHUB_TOKEN" ]; then
    perl -i.bak -pne "s/git\\+https:\\/\\/github\\.com\\/wtnabe/git\\+https:\\/\\/${GITHUB_TOKEN}\\@github\\.com\\/wtnabe/" package.json
fi

npm install や yarn install の前段階で package.json をゴリっと書き換えてしまっている。

また、単に https://github.com から始まる URI を書き換えるようにするといろいろ誤爆しそうなので git+https を入れたり工夫してある。

課題

  • private repository のコードを依存 package として指定したい
  • 通常の開発時は開発者は自分の認証情報をもとに GitHub などにアクセスしており、各自がいい具合にキャッシュとかさせているので問題ない
    • CI 上ではそういうわけにはいかない。なんらかの方法で認証をパスしなければいけない

試したこと

  1. ssh key
    • 最も素直な方法だが、GitHub には同一の鍵を複数登録できないので、CI のアクセスする repository の数が増えれば増えるほど扱いが大変
    • CI の機能で自動的に生成される鍵なら問題にはならないが、CircleCI では自動セットされる deploy key があるとそれを他の repository にも適用しようとするみたいでうまくいかない(そらそうか)
    • GitHub machine アカウントなら一つの鍵を使いまわせるっぽいけど、まだ試せていない
  2. ~/.netrc
    • 以前試した ~/.netrc を利用する技は CircleCI 上では使えないっぽかった。Heroku のように deploy 先で build プロセスを走らせることができない場合は build をすべて CI 上で動かす必要があるが、そこで使えないのなら別な方法を考えるしかない

GitHub/BitbucketからAccess Tokenを使ってdeployする - あーありがち(2017-04-17)

Tags: CI

2017-06-26 [長年日記]

_ BabelのcompactはGoogle Apps Scriptと相性がよくない、そしてGASプロジェクトは壊れたら直らない

結論

.babelrc に以下のように書け

{
  "preset":  ["es2015", "gas"],
  "compact": false
}

何が起きていたか

Babel の compact は 6.24.1 時点では 500KB 以上で適用されることとなっているが、これが適用されると eslint 的には OK だが Google Apps Script 的には NG なコードができあがる。ことがある。

Babel の compact は UglifyJS のようにアグレッシブなものではなく、本当に単純に white space を取り除いただけっぽく見える。読もうと思えば人間でも普通に読める。

ところがこれが適用されたコードで

(class)@294c297 is not function ...

みたいな謎のエラーが出るようになってしまった。該当箇所は Babel が compact したところのようだが、何が悪いのかは Script Editor は教えてくれない、何が function じゃないかも分からない。しかもどのようにコードをいじってもまったく同じエラーが出続ける。

そう、GAS プロジェクトそのものが壊れてしまったのだ。*1

おいおい。

待て待て。おれは賢いツールを組み合わせて賢くコードを作ってるじゃないか。これ以上何を望むんだい、と思ったが後の祭り。GAS プロジェクトが壊れたら新規に作り直してコードをコピーし直すしかない。

ふーっ。オーケー、分かったよ。降参だ。作り直そう。幸い deploy は自動化されているんだ、新しいプロジェクトを作るから deploy 先の ID は変わっちまうが、先日作った仕組みの中の JSON の ID をちょっと書き換えるだけさ。たいした影響はない。プロパティの設定も飛んだし、依存ライブラリ(Node.jsじゃなくてGASのライブラリ)も飛んだし、実行時の権限確認もまっさらさ。たいした問題じゃない。ちょっと関係者にメールすればいいだけ。幸い、GAS なんだから、共有してる人は対象の Drive 上のオブジェクトが共有されている人たちだ。そうそう、 [ File ] → [ Email collaborators ] っと…

めんどうくさいわ!

勝手に壊れんなや!

*1 実際には GAS プロジェクトが壊れたと気づくまでだいぶ試行錯誤を要したのは言うまでもない!