タグ "開発" が付けられた記事

Google App ScriptをTypeScriptとClass構文で書く - 実装

  1. 1. TL;DR
  2. 2. 想定
  3. 3. 開発
    1. 3.1. 基底クラスのconstructor
    2. 3.2. 基底クラスのメソッド
    3. 3.3. 継承したクラスを作る
    4. 3.4. Webhookをトリガーにしてクラスとそのメソッドを使う
  4. 4. デプロイと注意点
    1. 4.1. 注意点
      1. 4.1.1. GASでES6のimport/exportは使えない
      2. 4.1.2. デプロイと本番化
  5. 5. 最後に

前回は「Google App ScriptをTypeScriptとClass構文で書く - 環境導入」ということでClaspでローカル開発した場合の恩恵と、どう環境を設定したらいいかという話を書きました。

今回はES6から使えるようになったClass構文をつかって、TypeScriptでうまいことGASを書いていきます。本題です。
ちなみにこのClass構文はJSがそもそも持つprototypeの実装を簡単に書ける糖衣構文という位置付けです。

また、今回はTypeScriptに不慣れな人でもわかりやすくするためあえて型に関しての記述は少なくしました。慣れているほうは型をどんどん利用するともっと書きやすくなるのでオススメです。

Claspが正式にTypeScript対応してくれて事前ビルドなくtsをそのままpushできるようになって非常に楽になりました。その事情から逆に発生してしまっている特有の落し穴についても最後のほうで触れています。

TL;DR

  • GitHubのイベント(PRなど)をWebhookで受けてChatworkに通知するサンプル
  • ES6のClassと継承使うと責務の分離と共通動作の取り回しがしやすくなる
  • GAS(Clasp)+TypeScript特有の落とし穴があるから気をつけろ

想定

サンプルの題材としてGoogleSpreadSheetをDB的に扱って、その情報を参照して自動化する想定をします。例として、GitHubのプルリクエストの状態に応じてChatworkに通知をしてみます。具体的にはプルリクエストの状態の変化時にWebhookが飛ぶのでそれをGAS側で受けて、Chatworkの通知にしています。

SpreadSheetとしてはこんなかんじでシート2つあります。

  1. members: メンバーの名前、ChatworkのID、GitHubのIDを持っている名簿的なシート
  2. repositories: リポジトリ名、リポジトリのURL、対応する通知先のChatworkルームID

ちなみに各SpreadSheetの1行目は各カラムのタイトル行とします。

最終的にやりたいことは、登録したリポジトリでプルリクエストに変化があったらChatworkの指定したルームに関係者にToをつけて通知する、という流れです。これを細かくすると、

  1. GitHubでPRのイベントをトリガーとしてGASにWebhookを飛ばす(GitHub側で設定)
  2. GASで受けてWebhookの内容をパースする
  3. パースした内容にしたがって通知メッセージや通知先をGoogleSperedSheetから取得する
  4. Chatworkに通知する

という流れになります。

開発

基底クラスのconstructor

まず基底クラスとしてGasSheetというクラスを作ってみます。
こいつの役割は、各シートを扱うための情報の読み込みと検索、書き替えの機能を提供します。

GASのAPIでSpreadSheetのデータを範囲でとってきた場合、2次元配列になります。
したがって、例えば上の画像の名簿データは

[
['cwID', 'name', 'githubID'],
['1111111111111', '雨宮 蓮', 'joker'],
['2222222222222', '坂本 竜司', 'skull'],
['3333333333333', '高巻 杏', 'panther'],
// 中略...
]

という配列の中に各行が列ごとに値を区切られた配列として取得できます。

今回はこれだと扱いづらかったので、まずはGasSheetクラスをnewして生成したときに、
シートのデータをカラム名をキーに持つオブジェクトに変換して格納することにしました。

とりあえずnew GasSheet(sheet)な感じでシートを受けとって処理できるようにしてみます。

01_GasSheet.ts
export default class GasSheet {
sheet: any // SheetClass from GAS
columns: { columnNum: number; name: string }[]
data: {}[]
constructor(sheet) {
this.sheet = sheet
const rawColumns: any[] = sheet
.getRange(1, 1, 1, sheet.getLastColumn())
.getValues()[0]
const columns: { columnNum: number; name: string }[] = []
rawColumns.forEach((dataOfColumn, idx) => {
columns.push({ columnNum: idx + 1, name: dataOfColumn })
})
const rawData: any[][] = sheet
.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn())
.getValues()
const data: {}[] = []
rawData.forEach((dataOfRow, idx) => {
const obj = { rowNum: idx + 1 + 1 } // 行番号は1スタート + HEADERの行
columns.forEach((column, i) => {
obj[column.name] = dataOfRow[i]
})
data.push(obj)
})
this.columns = columns
this.data = data
}
}

使うときはたとえば

const SHEETS = SpreadsheetApp.openById(ここにスプレッドシートのID)
const MEMBERS_SHEET = SHEETS.getSheetByName(`members`)
const gasSheet = new GasSheet(MEMBERS_SHEET)
console.log(gasSheet.data[0])
// => { rowNum: 2, cwID:'111111111', name:'雨宮 蓮', githubID:'joker'}

みたいな感じですね。

基底クラスのメソッド

これだとまだ機能的にはオブジェクトの形になるようにラップしただけなので、メソッドから各データを取れるように実装してみましょう。

ちなみに配列、オブジェクト、コレクションを扱いやすくするためGASのライブラリとしても用意されているUnderscoreを使います。Underscoreにならって、今回は

  • where: 引数で指定したキーの値が合致する複数のオブジェクトを配列に入れて返すメソッド
  • findWhere: 引数で指定したキーの値が合致した最初のオブジェクトを返すメソッド

の2つを実装してみます。なお、本来なら見つからなかった場合など中でエラーハンドリングすべきですが、今回は省略します。

00_GasSheet.ts
const _ = Underscore.load() // Underscoreライブラリのロード
export default class GasSheet {
sheet: any // SheetClass from GAS
columns: { columnNum: number; name: string }[]
data: {}[]
constructor(sheet) {
// 中略(上で紹介した通り)
}
where(keyValue): {}[] {
return _.where(this.data, keyValue)
}
findWhere(keyValue): {} {
return _.findWhere(this.data, keyValue)
}
}

そして使うときは

const SHEETS = SpreadsheetApp.openById(ここにスプレッドシートのID)
const MEMBERS_SHEET = SHEETS.getSheetByName(`members`)
const gasSheet = new GasSheet(MEMBERS_SHEET)
console.log(gasSheet.findWhere({name: '坂本 竜司'}))
// => { rowNum: 3, cwID:'222222222', name:'坂本 竜司', githubID:'skull'}

みたいな感じです。

継承したクラスを作る

GasSheetクラスができたので、これを利用した別のクラスを作っていきます。既にオブジェクト指向的な言語に触れてる方にはいまさら説明の必要がないかもしれませんが、GASはいろいろな人が触っているようなので簡単に説明します。

例えばAというクラスを継承したA-1、A-2というクラスを作ったとします。AクラスがもつメソッドはA-1,A-2ともなにもせずとも使えます。ですがA-1だけのメソッドはA-2では使えません、逆もそうです。すごいざっくり言えば共通したいところは共通化すること、共通化しないところは個別でしか使えないという責務の分離の両方を実現できます。

じゃあ実際にやっていきます。

方針として、GasSheetクラスを継承させてMembersSheetクラスとRepositoriesSheetクラスを作っていきます。MembersSheetクラスは単純に任意の値から該当するデータを取得すればいいのでシンプルに継承したもの、Repositoriesクラスにはnotifyというメソッドを作って通知できるように実装します。

01_MembersSheet.ts
import GasSheet from './00_GasSheet'
const SHEETS = SpreadsheetApp.openById(ここにスプレッドシートのID)
const MEMBERS_SHEET = SHEETS.getSheetByName(`名簿`)

export default class MembersSheet extends GasSheet {
constructor() {
super(MEMBERS_SHEET)
}
}

MembersSheetはこれだけでOKです。注目すべきは、extends GasSheetと継承しているところ、そしてconstructor()は引数を使ってないところです。
superは継承元(GasSheetクラス)の同名メソッドを呼びますので、super()に引数をわたすことで先程の例でやっています。

const MEMBERS_SHEET = SHEETS.getSheetByName(`members`)
const gasSheet = new GasSheet(MEMBERS_SHEET)

と同じことをしています。

こうすると使うときは先程よりもシンプルになって

const membersSheet = new MembersSheet()
console.log(gasSheet.findWhere({name: '坂本 竜司'}))
// => { rowNum: 3, cwID:'222222222', name:'坂本 竜司', githubID:'skull'}

とするだけでOKになります。

次にRepositoriseSheetクラスを作ります。前半はMemberslSheetと同様です。

02_RepositoriesSheet.ts
import GasSheet from './00_GasSheet'
const SHEETS = SpreadsheetApp.openById(ここにスプレッドシートのID)
const REPOSITORIES_SHEET = SHEETS.getSheetByName(`repositories`)
const gasSheet = new GasSheet(REPOSITORIES_SHEET)

export default class RepositoriesSheet extends GasSheet {
constructor() {
super(REPOSITORIES_SHEET)
}
notify(notification) { // notification = {repo: url, to: id, message: msg }
// 最初にrepositoriesのシートからURLによりどのプロジェクトか特定する
const repo = this.findBy({repositoryURL: notification.repo})
// メッセージを引数で来たオブジェクトを使って整形する
const message =`[To:${notification.to}][info][title]${repo.name}[/title]${notification.message}[/info]`
// ライブラリ経由でAPIを叩いて通知する
const client = ChatWorkClient.factory({ token: ここにCWトークン })
return client.sendMessage({
room_id: repo.room_id,
body: message,
})
}
}

ChatWorkClientは非公式ですが、Chatwork通知用のライブラリがあるのでそれを使っています。
cw-shibuya/chatwork-client-gas: Chatwork Client for Google Apps Script

notifyメソッドでやっていることは、notificationという仮引数の名前でオブジェクトとして引数で、リポジトリのURL、 通知するメンバーのID、通知内容を取ります。

それにしたがって、メソッド内で適切な形にメッセージ内容や通知を飛ばす先のルームを設定しています。Chatworkでは[To: ID][title]などの独自タグで通知先や強調表示できます。普段Chatworkを使っていないほうは適宜そんな感じか、となんとなく見てください。

ここでのポイントthis.findBy()です。findByは継承元のGasSheetクラスに実装してあるので、使うことができます。つまりリポジトリシート情報からURLが合致するリポジトリの情報を取得しています。

CWトークンはコード内にベタで書くよりはPropertiesServiceなどを環境変数的に利用するのが良いと思いますが、ここでその説明は割愛します。

Webhookをトリガーにしてクラスとそのメソッドを使う

あともう一息ですね。ここまでで必要なクラスができたので、実際にWebhookを受けてメッセージを飛ばす実装をしていきます。

今回はサンプルとしてあるプルリクエストがマージされたときに通知するとしてみましょう。

GASの仕様でWebhookとしてリクエストが飛んできたものはdoPost()関数で受けることができて、その時のbodyに入ってくる内容は引数に渡せます(今回はeとして扱う)それを一旦パースして、そのあとで使いやすくしています。

doPost.ts
import MembersSheet from './01_MembersSheet'
import RepositoriesSheet from './02_RepositoriesSheet'
export function doPost(e) {
const contents = JSON.parse(e.postData.contents)
// membersシートを扱う準備
const membersSheet = new MembersSheet()
// membersシートからgithubIDの該当する人を探す
const membrer = memberSheet.findBy({githubID: contents.sender})
// repositoriesシートを扱う準備
const repositoriesSheet = new RepositoriesSheet()
// 通知内容をオブジェクトとしてまとめる
const notification = {
repo: contents.repositoryUrl,
to: member.cw_id,
message: `${contents.title}がマージされました!`
}
// repositoriesシートを使って通知を実行する
repositoriesSheet.notify(notification)
}

実装的にはMemberSheetクラスをインスタンスでwebhookに載ってきた情報からGitHubのIDから通知先をメンバーを特定します。

RepositoriesSheetクラスのインスタンスを作って、先程実装したnotifyメソッドに必要な情報を引数として渡しています。

ここでマージの場合はこう、レビューの場合はこう、みたいなハンドリングを省略しましたが、もしやりたい場合は書く必要があります。文章の出しわけも同様ですね。
今実際動いているものはシートのクラスとは別に例えばGitHubEventというクラスを作ってうまいことやるようにしています。

デプロイと注意点

注意点

あとは上記のスクリプト郡をデプロイすればいいだけですが、ここでGAS+TypeScript特有の落とし穴があります。

GASでES6のimport/exportは使えない

まさかと思いますよね、マジなんです。
じゃあ上のコードでimport/exportしてるのはなんでだ、って話なんですがこれはエディタの補完を効かせたりLintのためだったりです。実際$clasp pushするとコメントアウトされます。

さらにその特殊な事情として

import {
functionA,
functionB
} from `fileA`

みたいに書くとそのコメントアウトもまさしく働かくなってしまうのでやっちゃだめです。かなり罠です、お気をつけください。importを書くときは1行に書くのはGASでやるときは守っておいてください。

で、じゃあどうやって別ファイルに定義したものを使えるかというとGASは別ファイルに定義したものも他ファイルで使える全てがグローバルな仕様です。なので動かすだけならimportexportはいりません。
つまりimport/exportは使えないが結果的に同じことは実現できている、という状況です。

またimportがすべてコメントアウトされるためimportによる定義もできてません。なので、通常は自由な名前で定義できるところをClass名と厳密同じ名前でimportするようにします。

GasSheet.ts(export側)
export class GasSheet {
// 略...
}

import側
// ◯ 良い
import GasSheet from './GasSheet'

// × ダメな例(export時の名前と違う
import MySheet from 'GasSheet`

さらにこの仕様につながって、どうやらファイルはファイル名順に読み込まれ、読み込み前のものは使えない、という仕様があるっぽいです(要出展)。
なのでこのご時世としてはやりたくないですが、01_とか読み込まれて欲しい順でファイル名をつけます。

もう1つあります。直接使われる関数(e.g. doPost())はexport defaultしちゃうと上手く動きません。exportがある分には大丈夫ですがexport defaultとして宣言してはだめです。そういうこともあって先程のdoPost

export function doPost(e) {
// 中略
}

として定義しています。

デプロイと本番化

ClaspとGASの連携の話になりますが、通常Clasp経由でGASのコードを更新するには

$ clasp push

とします。これでGASのスクリプトエディタで開くコードが更新されます。もし開いたままだったらリロードしてください。

ちなみにClaspがTypeScript対応したことによる事前にtscなどは必要ありません。pushすると.tsファイルは.gsにトランスパイルされてアップロードされます。

ここでの注意点はなんか上手く反映されないときがあるので、リロードしたあと一度スクリプトエディタ上で保存すると上手くいくことがあるようです。このへんの挙動は謎です。

単純にGASにコードを追いて手動実行したりする場合はこれだけでいいんですが、Webhookを受けとるような場合ではWebアプリケーションとして公開する必要があります。また、公開するには版(バージョン)としてデプロイされていることが必要です。このため

$ clasp deploy

を実行します。この時引数をつけないで実行すると新しい版としてデプロイされます。

あとはGASのスクリプトエディタのほうで、公開 > Webアプリケーションとして導入とします。
この時に表示されるURLがWebhookを受けるURLなのでコピーしておいてGitHub側に設定します。
プロジェクトバージョンは先程デプロイしたときに発行されたバージョンを指定します。
他の権限の設定はやることによって最適なものが変わるので、設定します。

最後に

今実際に僕が動かしてるものはもうすこし多様性を持たせた結果、サンプルで扱うにはデカすぎるようになってしまったので公開して紹介が難しく残念です(もし改変して公開できる余裕ができたらぜひやりたい)

あと次回、もし続けばテストについて書けたらいいなあと思っています。

GASってVBA的に捉えてる層もいれば、JS系で書けるWebアプリとか自動化できるおもちゃみたいに考えてる層もいます。
この記事はそんな隔絶した層のちょうど溝を埋めるような記事として読まれたらいいなあ、と思っています。

Google App ScriptをTypeScriptとClass構文で書く - 環境導入

  1. 1. TL;DR
  2. 2. なんでやるの?
  3. 3. 開発環境
    1. 3.1. Linterとフォーマッターを導入する
      1. 3.1.1. ESLintでTypeScriptにLintをかける
      2. 3.1.2. ESLintにPrettierも組み込む
      3. 3.1.3. もっと細かい設定
    2. 3.2. 型定義の導入
      1. 3.2.1. GAS用ライブラリの型定義

GASは本当に手軽で便利。ほんのちょっと自動化したい、でもDB立てて、サーバー立ててまでやるのもなぁ、ってときにその溝を埋めてくれる良いところに収まってる感じがしますね。特にGoogleスプレッドシートとの連携もしやすいからスプシを簡易DBとして見たててやるとけっこういろいろできちゃう。

そんな感じで職場の社内ツール的なものをGASで作ってたりするんですが、昨年後半にClasp経由でTypeScriptがサポートされたのでちゃんと書きなおしてみました(以前はWebpackでやっていました)。それが一段落したのでせっかくなのでその知見をご紹介しようと思います。

書いてたら長くなってしまったので、いくつかに分けます。まずは環境構築から。

TL;DR

  • GoogleAppScriptはClasp経由でローカルで開発できるぜ
  • ローカルで開発できるってことはGitが使えたり、静的解析も使えるぜ
  • TypeScriptにも対応してるのでいろんな恩恵があって最高だぜ
  • そんなことを実現するための設定を今回は紹介するぜ

なんでやるの?

まずなんでこの路線で開発するか、というポイントは

  • TypeScriptはES6な書き方ができる
    • ESLint(TSLint)、Prettierなども使える!
    • Class構文便利!
      • ちゃんと継承もできるんだぜ
  • TypeScriptの型サポートがあると書きやすい
  • TypeScriptがサポートされたから面倒なWebpackが要らなくなった

ES6による恩恵が一番大きいので、以前からWebpackでやっていた場合は大きな変化ではないですが、Clasp側でサポートされたことによって自前でビルドする手間もなくなったのが大きいですね。
特にWebpackは時に設定がややこしく、そこでつまる人も多いと聞くので脱WebpackしつつもES6の恩恵を受けれる環境ができあがったのが嬉しい限りです。

開発環境

まずなにはともあれClaspを入れます。これは通常Web上のスクリプトエディタでGASのコードを書いていくのではなく、ローカルのファイルとしてスクリプトを書けるようにしてくれるものです。
ローカルで扱えるというだけで様々な利点があります。

  • Gitが使える
    • バージョン管理が楽になる
    • つまりもちろんGitHubによる共同開発環境が持てる
  • ESLint(TSLint)、Prettierなどの静的解析によるフォーマット、リントが効く
    • 間違いが減ったり、自動修正したり
  • 好きなエディタが使える
    • 捗る!

ここでは詳細なClaspの導入、GASとの反映方法は割愛します。公式のREADMEやちょっとググればいろいろな導入記事が出てくると思いますのでそちらをご参照いただればすぐできると思います。

あ、ちなみにNode.jsも必須です。その導入もここでは触れません。

今回の開発で特殊な事情を加味して言及するとこんなディレクト構成になります

.
├── __tests__
│   └── 各種テスト用ファイル
└── node_modules # ライブラリ格納ディレクトリ
└── src
│   ├── *.ts # これから作っていくTypeScriptファイル
│   └── appscript.json # GASの設定ファイル
├── .clasp.json # claspの設定ファイル
├── .claspignore # clasp用のignoreファイル
├── .eslintrc.js # 後述するESLint用の設定ファイル
├── .gitignore
├── index.d.ts # 型定義ファイル
└── package.json # プロジェクトの設定ファイル

とこんな感じですかね。テストは書かないんだったらないですが、せっかくローカルで開発するならテストも書きたいところです。
なので環境を整えた後はsrc/以下にガリガリ実装していく感じです。注意すべき点としてはGASの設定用のappscript.jsonはこのsrcディレクトリ内に置くことになるところです。

Linterとフォーマッターを導入する

いくつか選択肢はあると思いますが、今回は

  • ESLint経由でTypeScriptのLintをする
    • 理由: TypeScript側が「LintはESLintを使ってくれよな」って言っている
  • ESLint内でPrettierによるフォーマットをかける
    • 理由: Lint側とバッティングするルールがあるので上手く避ける

という方法で行きたいと思います。

一応TypeScriptでTSLintではなくESLintを推奨している経緯は

のLintの項目、もしくはThe future of TypeScript on ESLint - ESLint - Pluggable JavaScript linterを参照していただければと思います。

ESLintでTypeScriptにLintをかける

yarn add --dev eslint
yarn add --dev @typescript-eslint/eslint-plugin
yarn add --dev @typescript-eslint/parser

で、ESLintとESLint経由でTypeScript対応するプラグインとパーサーを入れます。
そしたら.eslintrc.jsというルール設定のファイルを作って、

.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
}

と入れます。これが最小限の設定ですね。

これで.eslintr.js内にTypeScript用のLint設定も書いて設定ができます。

ESLintにPrettierも組み込む

ESLintの設定のいくつかはJS系の最有力フォーマッタであるPrettierと一部バッティングするルールがあります。
これが整合性が取れてないと、自動でPrettierのフォーマットとesLint --fixを連続してかけたりエディタの設定でFixOnSaveとかやってると矛盾ルールでハマります。

ということで個人的なオススメとして、eslint --fix内でPrettierをかける設定にするのが良いと思っています。またVSCodeなどでESLintのfixOnSave設定だけでキッチリPrettierもかかります。

具体的には

yarn add --dev prettier
yarn add --dev eslint-plugin-prettier
yarn add --dev eslint-config-prettier

eslint-plugin-prettier.eslintrc.js内でPrettierの設定もできるようにするもの、eslint-config-prettierはESLint側のPrettierのフォーマットルールとバッティングするルールをオフにするものです。

となると、設定は

.eslintrc.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'prettier',
],
extends: [
'prettier',
'prettier/@typescript-eslint',
],
rules: {
'prettier/prettier': ['error', {
useTabs: false, // example
}],
'no-var': 'error', // example
'@typescript-eslint/camelcase': 'error' // example
},
}

みたいな感じが最小になりますかね(Ruleに関してはサンプルで入れています。適宜カスタムしてください)。

このあたりのことはIntegrating with ESLint · Prettierを参照していただければわかりやすいかと思います。

もっと細かい設定

ここはオプショナルな設定ですが、用意されているルールなどを適用したい場合は、もうちょっと込み入ってきます。僕はStandard(JavaScript Standard Style)派なんですが、例えばそれを適用しようとするなら、standard/eslint-config-standardを使いますので、

yarn add --dev eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node

として.eslintrc.jsのextendsに'standard'を加えます。Standardにはno-undefルール(定義されていないものに警告するルール)が入ってますので、GAS特有の関数(e.g. SpreadsheetApp)が警告されます。

.eslintrc.jsglobalsに設定してあげればいいんですが、それなりの数があるのと思うので、selectnull/eslint-plugin-googleappsscriptを使ってガっと回避します。

ちなみにオブジェクト操作のライブラリUnderscore.jsや日付を扱うライブラリMoment.jsがGASでも用意されてますが、使う場合は同じようにグローバルな関数になるので、それは.eslintrc.jsglobalsで設定していきます。

そうなるとこんな感じになります

.eslintrc.js
module.exports = {
root: true,
env: {
'googleappsscript/googleappsscript': true,
},
globals: { // example
Underscore: true,
Moment: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
plugins: [
'googleappsscript',
'@typescript-eslint',
'prettier',
],
extends: [
'standard',
'prettier',
'prettier/@typescript-eslint',
],
rules: { // example
'prettier/prettier': ['error', {
useTabs: false,
}],
'no-var': 'error',
'@typescript-eslint/camelcase': 'error'
}
}

ちなみに僕の場合、PrettierとESLintともにもっと細かくルール設定しています。
そして、Husky経由でGitのcommit時に自動でeslint --fixがかかるようになっています。
その辺のことは以前書いた記事をご参照ください。

LintとFormatをGitのコミット時に自動でかける方法 - Trial and Spiral

型定義の導入

これでようやく環境が整った! と思いきやまだあるんです。そうです型定義です。
ありがたいことに公式でGAS関数の型定義が用意されているのでサクっと入れます。

yarn add --dev @types/google-apps-script

これでGASの関数に関してはばっちり型サポートが有効になります。

GAS用ライブラリの型定義

GAS用ライブラリを導入した場合、多くはグローバル関数として使えるようになります。しかしローカルで開発するときはそんなことはわからないので、そんな関数の型は定義されてないぜ、っていう警告が出ます。
それを回避するためにindex.d.tsファイルを作って、例えばこんな感じに書きます。

index.d.ts
declare const Moment: {
moment(arg?: any): any
}
declare const Underscore: {
load(): any
}

これは僕の中でまだ上手くいってない部分で苦肉の策です。MomentもUnderscoreも同名のJSライブラリが元になっていてすでに型定義ファイルが用意されています。使い方がちょっとだけ違うのでそこを上手く吸収しつつ、型定義をうまく流用できたらいいなあと思っています。

どなたか解決方法があったら教えていただけると嬉しいです。

そんな感じで長くなりましたが環境構築ひとまず完了です。
次回は実装編を書けたらいいなぁ。

Git 初回コミットのメッセージをちゃんと決めてみる作戦

  1. 1. なんで決めるか
  2. 2. 一般的にどうか
    1. 2.1. 調べてみた
  3. 3. 僕はinitに決めた
  4. 4. ちなみにrebaseはできる

新しいことをいろいろやろうとすると、それがSandboxであれgit initすることはそこそこあると思う。で、毎度毎度うっすら「初回のコミットメッセージって何か決まりあるんかな」と思うけど、まあわかればいいか、と思って適当にやるっていう感じだった。今回はそれをちゃんの決めてみようというお話。

なんで決めるか

僕の行動原理として「小さなことでも何度か考えるなら論理的にルールを決めると楽」という実感がある。
これはインデントがどうとか、コーディングフォーマットにも通じるんだけど、要は本質じゃないものからのノイズを減らして本質にフォーカスしやすくする、という戦略のつもり。
余談だけど僕がSCSS嫌いでSASS好きなのもそういう理由だったり(Stylusはもっと好き)。

なんかカッコつけたけど、毎回3秒でも悩みたくないから決めちゃっておきたい、でも自分の中だけにでも納得のいく論理的理由が必要、という面倒な自分を御したいだけな話。ちょっと最近いろいろと忙し気味なので、そういう時はこういう簡単なことをサクっと処理する時間にあてていきたい所存。

一般的にどうか

そもそも絶対こうじゃなきゃいけない、というものはない様子。ならばまず最初に狙うべきは「一番多い」というところ。 Gitは複数人で扱う前提とすれば「誰もが迷わずそれが初回コミットであることを認識できる」というのが必須条件になってくるはずで。

そうすると良く見かけるのはfirst commitinitial commitのどちらかだろうと思う。ちなみに今までの僕はinitial commitを使ってきてる。
これは予想だけど、僕も含めGitを初めて扱い始めたころのチュートリアル的なものに影響される人が多いんだと思う。

もう1つ日本語メインでやっている人の場合は「初回コミット」というのも見かける気がする。考慮すべきはまあこのあたりかなぁ。

調べてみた

ちょっと面白い記事を見つけたので紹介したい。

すごい簡単に説明すると、スクリプト書いてある分野のGitHubのリポジトリの最初のコミットメッセージを集計してみた、的な感じ。

これを見る限りではやはりfirst commitinitial commitが郡を抜いて多い。ついでadd readmeのようで、これはとりあえず最初にreadmeだけアップした、みたいな感じなのだろう。
タイポや省略形もあるのでザっと見た感じで分けると

  • first派(1st commit, first commiitなど)
  • initial派(init, initial, initial commitなど)
  • add派(add files, add readme, create readme, など雑多)

の三種類に大別できるかなーって感じ。

注意すべきは対象がrOpenSciのものだけみたいなので、少し偏りがある可能性も否めない。

僕はinitに決めた

まあ「最初のコミットだよ」ってわかればなんでも良さそう。先程の例に習って僕もスクリプト回してみようかと思ったけど、結局やらずに決めてしまった。

僕はinitで行こうと思う。

理由として、

  • commitという情報はそれがコミットメッセージである以上、書かなくてもわかるので省略
  • Gitの最初のコマンドは$ git initですので
  • 正直わりとどうでもよくなったので短かくてわかりやすいのでいいやってなった

という理由。
これでもう初回のコミット時にメッセージどうしようか思いを馳せることはなくなった、1リポジトリあたり3秒ぐらいの時間的コストをカット、同時にその一瞬思考をそっちにまわす脳内ワーキングメモリコストもカット(わりと無視できる程度な差なのは否めないw)

ちなみにrebaseはできる

今回初回コミットについて調べる過程でメッセージとは違う軸の話として、

最初のコミットは前コミットが存在しないためrebaseができない、だから初回コミットはあえて空コミットにする

みたいな話を見かけたけど、よくよく調べてみると

$ git rebase -i --root`

とか--rootオプションで一番最初のコミットを含めて修正できるし、実際試したら出来たのでこれは採用しなかった。

ただ、最初を空コミットにしてcreate this repositoryとかにしてもアリかなぁとも思った。いやいややっぱりナシで、僕はもうinitに決めたんだ、迷わないんだ!

コンポーネント時代のCSSの命名ルールを考えてみよう

  1. 1. ScopedなCSSとは
  2. 2. デファクトスタンダードなBEM
  3. 3. 僕はSMACSSが好き
  4. 4. オレオレルールを模索する
  5. 5. まとめ

このブログはHexoというNode.js系の静的サイトジェネレータで作ってる。テーマはイチからフルスクラッチで自分で作った。でも自分で作ったがゆえにまだ手のゆき届いてないところがあったり。そんなこんなで久々にテーマをどう作ってたってところから見なおしてたり。

そんなことから、そういえば以前はBEMやらFLOCSSだったり、SMACSSのルールに基づいてCSSを書いてたなあ、と思った。一方、React, Angular, Vueの三大巨頭が牽引するコンポーネント時代に突入してきた今、CSSの効果範囲はScopedにものが主流になってきている。ScopedなCSSはそれに適したまた別の命名ルールが存在するのでは、と思って考えてみたという話。

ScopedなCSSとは

まずScopedなCSSについて簡単に説明したい。
そもそもScopedなCSSというのはJS系フレームワーク産の発想ではなくて、一時期はちゃんとFirefoxにも実装されたようにCSSそのもので提案された考えかた。

CSSは名前のとおりカスケーディングに処理されているけれど、カスケーディングがゆえに乱暴に言えば全てがグローバルに宣言されていると同じようなもので、そこから、あるDOMの中でだけに絞って作用させたい用途としてScopedが生まれたんだと思う(この辺あいまいです……)

残念ながらそれは正式に採用されることはなくなったようだけど、WebComponentよりも先駆けてコンポーネント指向の急先鋒となったReact, Angular, Vueには実装の形は違えど、各コンポーネント内だけでしか作用しないCSSの書き方がある。それを今回は便宜上「ScopedなCSS」と呼んでいる。

デファクトスタンダードなBEM

さて、Scopedじゃない時代、人は常にCSSの影響範囲と戦ってきた。基本的にはそれぞれにクラスをちゃんと指定して、クラス名の命名規則とつけかたで頑張りましょうという戦略。

そこで生まれたのがBEM(Mind Bemding)という手法。block__element--modifierという命名規則で、親子関係がしっかりしているのが特徴。OOCSS(オブジェクト指向CSS)としても構造がわかりやすく、今やCSS命名規則のデファクトスタンダードと言っても過言ではなさそう。

僕はSMACSSが好き

BEMもSMACSSもどちらもある単位ごとに分けて管理しよう、って概念はかなり近い。個人的にはBEMよりはSMACSSのほうがわかりやすい気がする。その反面、Mに相当するモジュールの自由度が高くて悩むこともしばしばある。でもそれはBEMにしてみてもブロックでの区切りかたに悩むのと同じようなもので。

SMACSSが大きくBEMと違うのは、ステート(状態)をマルチクラスとして宣言するところ。BEMの場合で言えばModifierに相当し、例えば同じように書くとしたらBEMなら.module--stateと指定してたのをSMACSSなら.module.is-stateというような感じ。

個人的にはこちらのほうがCSSの特性を考えると、状態の変化を上手くあつかうには適していると思っているのでこっちのほうが好きだ。

オレオレルールを模索する

じゃあコンポーネント指向になった構造でScoped CSSになったとき、どうするのが良さそうか、っていう話。

AtomicDesign的な思想をベースとすると、コンポーネントの単位はその「責務」によって分割されるべきで、これがCSSのデザインのブロックやモジュールとは非常に似つつも完全に同一でないのでちょっと混乱しやすい。

まず、SMACSSで言うようなレイアウトのプレフィックスはいらないくなってくるはず。つけたいなら付けてもいいけど、Scopedである以上、レイアウトを扱う部分とモジュールの部分は別コンポーネントになるべきだ。もし必要になるのであったらそれはCSSの話じゃなくてコンポーネントを責務にわけた設計にしたほうが良い。

コンポーネント内でのクラス名はBEM的なのが良さそう。BEMのツライところとして、1つのまとまりだけどHTMLの構造上Elementが多層になったりするとBEMのルールだけではなかなかやりづらいようなことがある。BEEMとかにしたいときとかあるはず。BEMの規則にのっとるとこれはダメなので別のブロックとしてとらえてまた別の命名を考える、となるんだろう。けどコンポーネント指向の場合、そんなに多層になるのであればそもそもコンポーネントの切りかたが悪そうな気もしてくる。

BEMで言うモディファイア、SMACSSで言うステートは、SMACSS流にのっとってis-stateとかhas-stateisもしくはhasプレフィックスをつけて、変化するところだけを指定する。これはその状態が変化したときには、SMACSS的にマルチクラス的に指定してクラスを付けかえるほうがやりやすい。hasも加えたのは、isだけだと意味がおかしいことがあるので。

その結果、僕が「これでいってみよう!」と思ったのは、Scopedなコンポーネント内だけで

.some_block--some_element.is-state

というような書きかた。

説明すると

  • 複数語の場合、単語の区切りは_(アンダースコア1つ)
  • 階層的になる場合、--(ハイフン2つ)でつなげる
  • 状態の変化した場合の指定はis-もしくはhas-というプレフィックスのクラスを足す

ここでBEMに慣れ親しみすぎてると「おいおいハイフンとアンダースコアの使い方が変じゃない?」と思うかもしれない。僕がこう定めてみたのにはちゃんと理由があって、文字の選択をする場合などで

  • ハイフンで区切られた単語はハイフンを挟んで別の単語として認識される
  • アンダースコアで区切られた単語は1語として認識される

という特徴があるため。試しに上のsome_blockとかsome_elementとかをダブルクリックとかで選択してみてほしい。

ブロックと要素の区切りがハイフン2つにしたのは、BEMがやってるように、前後は別のまとまりというのを認識しやすくするため。単語の区切りが1つはなのはそれとの差別化。これでBEMに慣れすぎていても、blockとelementは違うくくりだな、と混乱しないようになってると思う。

まとめ

ScopedなCSSをメインで書くようになってから、命名ルールすら適当でも問題なくできちゃうぐらい便利で、今までなあなあで来てしまってた。それでもやっぱりルールは欲しいもので、あっちではBEMっぽく書いてこっちではSMACSSっぽく書いて、みたいにやってしまってたのでなんかずっと喉につかえた小骨のように気持ちわるかった。

単純な静的なページでもReact, Angular, Vueなどが使われはじめてみんなScopedなCSSで書けるようになってきていて最近は以前ほどOOCSS派生のルールみたいなものを聞かなくなってた気がする。それでも今回、一応自分の中では「こういうルールで行く!」と思えるようなものを決めれたのはなかなか良かったと思う。

他のみなさんはこのコンポーネント時代のCSS命名をどのようなルールでやってるか凄く興味があるなぁ。

LintとFormatをGitのコミット時に自動でかける方法

  1. 1. 使うもの
    1. 1.1. Husky
    2. 1.2. lint-staged
  2. 2. インストール
  3. 3. 設定
    1. 3.1. まずhuskyの設定
    2. 3.2. lint-stagedの設定
  4. 4. おまけとまとめ

前回、LintとFormatをかけるのはもちろんなんだけど、なんでGitHookのタイミングにしたかって話を書いた。今回はそれの具体的な方法を書いていこうと思う。

ちなみに前回書いたやつ。

使うもの

まず使うNPMのライブラリを使うので、Node.jsを用意してください。これmacならbrewでyarnを入れてもいいし、僕はanyenvを経由してndenvを使ってる。いろんなやり方があるし、それぞれ利点が違うので自分にあった入れ方を推奨。

それで使うライブラリなんだけど

  • husky
  • lint-staged

の2つ。以下で簡単に紹介しよう。

Husky

huskyはGitHookをプロジェクト単位で設定できるようにするツール。
GitHookの設定は通常だと{project root}/.git/hooks/配下にある。.git以下は通常ではリポジトリに含まれないので共有されない、つまりhookを共有したり強制したりできない。

だから、husky経由で共有できるところで設定できるようにする、というのがhuskyの役割。どうやらhuskyを入れたときにGit hookをhusky経由で動くように入れかえてるっぽい。

lint-staged

lint-stagedはgit addした対象に対して特定のコマンドを走らせるもの。READMEにもあるとおりhuskyと合わせて使うのがオススメとされている。ちなみに以前はhusky以外にもpre-commitをやり方やいろいろあった。あと、名前はlint-stagedだけどやることはstagingされたコミット前のものに対して何かをするのでlint以外にも使える。

ちなみに昨年前半あたりまでずーっとハンク(git add -p)したファイルの対応する方法に関してissueで議論されていたが、昨年中頃にlint-stagedがアルファ版を経て正式に対応した。個人的にハンクはかなり使うのでずっと動向を追っていたし、これが解消されたからプロジェクトに導入したし、紹介するに相なりました。

インストール

npmに慣れてる人には言うまでもないだろうけど

$ yarn add -D husky lint-staged
# or npm install -D husky lint-staged

でOK。

設定

package.jsonに設定を書いていく。ちなみに別ファイルに分けることもできるけど、そう複雑で長い設定でもなく、huskyとlint-staged両方とも連続した設定になるし、package.jsonのscriptともかかわることもあるので個人的にはpackage.jsonだけでやっちゃうのが良いと思ってる。

まずhuskyの設定

以下を追加

package.json
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},

要はhuskyの設定を、hooksのpre-commitにlint-stagedコマンドをするよ、という設定。
ここではpre-commitだけだけどもちろん他のhookも同様に書くことができる。詳しくはhuskyのREADME参照。

lint-stagedの設定

同様にlint-stagedの設定もpackage.jsonに書く。ここはどのようなファイルにどのようなlintをするかので一例として

package.json
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix",
"git add"
]
},

みたいに設定する。
lint-staged直下の設定で対象のファイルのglobパターンを書く。例えば上の例だったらGitのStageにある拡張子がjs,ts,vueのものにeslint --fixがかかる。これで自動で修正された結果のファイルが再度addされて、問題なければcommitされる。

もちろん、lintの結果エラーが返ったときはcommitされない。意図的にpre-commitフックを避けない、という前提のもとであればコミットされたものは全てプロジェクトで設定されたLintのルールにパスしていることが担保される。

で、もちろんファイル名の条件にしたがってコマンドを走らせるだけなので、

package.json
"lint-staged": {
"*.{js,vue,ts}": [
"eslint --fix",
"git add"
],
"(*.rb|*.rabl|Gemfile)": [
"bundle exec rubocop --auto-correct --fail-level E",
"git add"
]
},

みたいにやればこの例で言えばJS系だけでなくRubyのファイルにRubocop(Ruby用のLinter兼Formatter)をかけるようにもできる。

ちなみにJS系のformatterとしてprettierがあるけど、eslintのルールとバッティングするところもあるので、eslintにpluginを噛ませてeslintの中でprettierによるformatするのがお勧め。

おまけとまとめ

あとは以前に紹介したcommitizenもNPMなので一緒に使うのも簡単で、そうするとコミットメッセージも綺麗、コミット内容もキレイ、という素敵なコミットができる。

と書いてきたけど、やってることはいろんなところで紹介されてるしそんな難しいことじゃない。
でもNPMがフロントエンド特化傾向にあるため、Node.js系をふだん使ってない人には有用であればうれしいなぁ。特にRailsはWebpackerやらYarnを採用したこともあって相性は悪くないと思う。

途中でもちょっと触れたけどこのGitのハンクに対応したこともあって、この仕組みを業務でも使ってみたけど上手くいってますのでけっこうオススメしますよ。

LintとFormatをGitHook時にかけてる理由

  1. 1. いつかけるか
    1. 1.1. Save時
    2. 1.2. GitHookにひっかける
    3. 1.3. Pull Request時にCIで回す
  2. 2. もうひとつの目的
  3. 3. 実践

LinterとFormatterをかけないコーディングはツラくて個人的にはちょっと悲しくなるんだけど、問題はどのタイミングでやるかってこと。個人的にやっていたものを昨年なかごろから業務にも取り入れてみてそこそこ上手く回っているので書き残しておきたい。

いつかけるか

LinterとFormatterをかけるとしたらいつがいいのかいろいろ考えて、結論から先に言えばGit commit時にしている。なんでそう結論づけたかっていう話の比較を交えてご紹介したい。

Save時

一番頻繁にかかる一例としてファイルの保存時。例えばVSCodeなんかではFixOnSaveとかって設定があるようにわりとメジャーだと思う。

Saveするときにかかるので一番頻繁だし、適当に書いてもガっとキレイにFormatは効くし、変なコードはすぐに警告が出る。良い。

でも残念ながらできるかどうかははエディタ頼みになってしまう。独りで開発しているときは十分かもしれないけど、みんな違うエディタだったり対応できないエディタの使用者がいたら?

例えもし開発者それぞれが使うエディタで全部対応できたとしても、エディタごとに設定もプラグインとかも違ってくるのでどうしてもノウハウが属人化してしまう。

一番大事なのは自分以外も加わるとしたら強制できないのであまり有効でないところ。ルールを適用するならそれは守られなければ意味がない。

GitHookにひっかける

GitにはGitHookという機能があって、commit時やpush時に設定したスクリプトを回すことができる。最初に言ったようにこのフックにひっかけてpre-commit時にLinterとFormatterをかける運用にしている。

理由はcommitをしないことには開発は進められないし、エディタがなんであろうと強制できる。そしてローカル上でエラーが出るのでそのcommitする開発者が自分でエラー箇所を修正することになる。また最悪どうしようもないときは--no-verifyオプションをつけることで一時的にGit Hookを飛ばすこともできる。

ちなみにpre-pushも検討してみたけど、push時に弾かれたり修正が入ることでコミットログがエラーやコーディングスタイルの修正だけになったりして、それは別コミットにすべきじゃないと思ったのでpre-commitにした。

Pull Request時にCIで回す

他の候補としてPull Request時にCI等の外部サービス経由で回すことももちろん検討した。これも結局はpre-pushと同じようにコミットをキチンと整理したいこと、その後のマージ戦略まで影響するのがちょっと面倒で見送った。

ただし方法としては一番、開発環境に依存しない方法だし悪くはないと思う。pre-commitにしろpre-pushにしろ、CIにしろ、ポイントとしては

  1. Lintに通ってないコードを採用しない(できない)。
  2. Formatterのルールにのっとってないコードを紛れこまさない
  3. 1と2を共同開発者全員で共有する

というのが前提として守りたいルールだった。

もうひとつの目的

なし崩し的にチームリーダー的なポジションになって後輩を抱えるにことになったけど、なにもイチから教えることはなくて「こういうコードがいいよね」っていうのはLinterやFormatterに助けてもらうことにした。

なにより僕がレビューコメントや口頭で「こういうコードがいいよ」っていうより、ルールの説明のドキュメントのほうがよっぽど説得力がある。

まずチームにJoinすることになったときにルールを確認してもらって、できればその時になんでそういうルールを設定してあるかもコメント等で共有する。そしてそのルールに納得してもらうようにした。反論は出なかったものの今でも合わないルールがあったら変えるからいつでも相談して欲しいとは伝えてある。

そうすることで例えばLinterではうっかり使用されない変数、到達しえない分岐、デバッグ用のコードなど紛れこんだままだったり、同じ結果だとしてもよりリーダーブルな書き方だったり、そういったことがPull Requestに紛れこまないことによってレビューの時間コストも抑えれるし、エラーが出たら自身でちゃんとルールを参照して修正してもらうようにしてる。そうすることで自身で徐々により良いコードを書けるようになってもらう作戦。

またFormatをキチっとやるのことも僕は大事だと思っていてインデントが不揃いだったり、なんか感覚的に気持ち悪いっていうことだけじゃなく、コーディングスタイルを統一することで読みやすくして、本質じゃないノイズを極力カットすることで本質にフォーカスしやすくするのが狙い。

この目的にあたって注意したのは「ルールは聖域、無視しちゃダメ」ということ。もちろんキビしすぎるルールは時に枷となって開発スピードを鈍化させたりもするんだけど、そういう場合もキチっと直すことを意識した。なんども苦労するようであればそれはルール自体を変更したり緩めたりすることで対応する。

これはLinterのルールは割れ窓理論的に悪化しやすいと思っていて「ああ、このルールはまあ無視してもいいよ」みたいにしてしまうと次第にどんどん広がっていき、最終的にはルールは守られず、無いもの同然になってしまう。その経験が僕にはある。だから「ルールは聖域、無視したいのであればルールの変更を提案しよう」として運用して、事実上手くいってる。

実践

じゃあpre-commit時にLinterとFormatterをかけるための具体的な方法なんだけど、それはちょっと次回に回したいと思います。使ってる言語と環境にも寄るんだけど、僕の主戦場がRubyとJS(TS)なのでそれら限定になってしまう。とはいえフックさせて回すのはNPMライブラリだけでやってるので他の言語のプロジェクトでもNPMを同時に使えば実現できそう。

そんなhuskylint-stagedを使った具体的な設定はまた次回。

追記
書きました。

ここ1年ちょっとで僕が作ったプロダクト

  1. 1. Pentazemin
  2. 2. MHW-cheatsheet
  3. 3. YANTAN
  4. 4. cz-conventional-changelog-ja
  5. 5. その他
  6. 6. 所感とこれから

いよいよ2018年も終わりそうで。今日は職場の大掃除があってようやく年末感を味わいはじめた。せっかくなので今年に作ったもの達を振りかえってみたいと思う。ちなみに開発系限定で。というのも開発系にフルコミットしたからなのか、単に気が向かなかったのかレザークラフトとか物体の物作りは全然してない。

Pentazemin

いわゆるポモドーロテクニックを利用したタスク管理アプリ。どっちかというとタイムマネジメントの側面が強い。気になった人はリンク先を見ていただけると嬉しいです。

時間的には去年、2017年の終わりごろにリリースしたアプリなんだけど、当時はまだVue.jsもそこまで人気じゃなくNuxt.jsのver1でさえベータ版だった。そう思うと2018年のそのあたりの速度って凄かったなぁ。

こういうアプリが欲しいなぁと思いたって、ちょうど今Vue.jsやってみたし、おっElectronも勉強しながらやったら面白いんじゃない? と思って作った。そしてこの時にガッツリVue.jsに触れて苦戦しながらもちゃんとリリースまで持ってったのが今の僕のVue.jsの基礎力となってその結果素晴しく力がついた。

作ってできたーじゃなくて、使ってもらうために紹介ページを作ったりして、いくつかのブログで紹介されたり、外からお声がかかるきっかけになった。開発力だけじゃなくて作る以外のことも含めて僕の2018年を支えてくれたプロダクトでした。

2018年いろいろ作ってきてレベルアップできた感があるので、今の技術でちゃんと一新したい。

MHW-cheatsheet

年の始めにMonsterHunter Worldというゲームが出て、夫婦ともにガッツリハマって。簡単に説明するとみんなで協力しながら大きなモンスターを狩る、みたいなゲーム。で、モンスターには属性や攻撃部位による弱点があってなかなか複雑。

ネットを見てたら早見表みたいなのが凄くバズってましてね。絶賛されてるんだけど、個人的に「おいおいまだ見やすくできるだろ」という思いが膨らんで、よっしゃいっちょ作ったるか、せっかくだからVue.jsでPWAやってみよって作った(とはいえ、最初に画像でもなんでも形にした先人達には敬意を表したい)

やってて思ったのは、開発自体は楽しいんだけどモンスターの情報入力とかは地道で面倒で辛かった。あとゲームプレイしたい時間を削って開発するのも辛かった。

でも一番辛かったのは、宣伝するのが難しくて火がつかなかったこと。使ってくれた周りの人とかごく一部の僕のTwitterフォロワーの人達にはすごく評判良かったものの、当時ゲームの全盛期でハッシュタグ付けても文字通り秒で流れていくし、目にとまらない。個人レベルでの宣伝って難しいのを思い知らされた。

技術的にはPWAに触れれたし完全に静的にアップするんでもアイデア次第ではいろいろ活用できるなーって感触が得られたのは大きい。

YANTAN

新しいタブ用の拡張って前からしっくり来てなくて。と言うのも綺麗だけど自分がとった写真じゃなくてどっかの誰かがとったやつだったりとか。あとは単純なメモが欲しかった。ある日に、あれ、SPAの技術使えばChrome拡張って作れるんじゃね? とひらめいたから作った。

CSSでかけれるフィルタをフルに搭載して、カスタマイズ性の高いものが作れたのは良かったし、なによりこれも使ってくれた人からけっこう評判良く、自分でも常用している(そりゃあ自分が欲しくて作ったわけですし)

苦戦したのは、Markdownのリスト記法の入力保管とか。入力保管って自分で実装すると以外に泥くさいことしなきゃなんなくて。とはいえなかなか楽しかった。あとこれもマーケティング的に上手くいってない。やっぱり自分のTwitter、GitHub、ブログ以外のところで上手いこと宣伝活動していかないといけないんだなぁと思う。

cz-conventional-changelog-ja

いろいろ作ってたなかでCommitizenが凄く良くって、もっとGitのコミットメッセージをちゃんと書く文化が周りにも広まればいいなぁと思ってガっと訳した。

エゴサーチしてみると影響を受けてフォークして別の何かを作ってくれたりもしてくれてるようだし、嬉しいかぎり。また、翻訳フォークとはいえNPMライブラリデビューできたのは良かった。

自分は今はcz-customizeを使ってるので、使ってないけど今もすこーしダウンロードされてるようなので嬉しい。

その他

世に出したのはこれぐらいで、業務のわりと余裕がある時期にGASを使ってGitHubのプルリクとかをChatworkに通知されるWebHook作った。GASはスプシでデータ持てるし、タダでWebアプリ的なことができるから思ってるよりアイデア次第でいろいろ化けそう。このネタで外部でLTもしたっけ。

あとはRailsとVueでイベントのペライチのWebページを作れるWebアプリのプロトタイプも作ったっけ。これはいろんな技術の中で模索して、結局リリースレベルに持っていく前に頓挫してしまった。でもアイデアはアリだと思うのでいつかちゃんと形にしたい。

所感とこれから

こうしてみると仕事以外でそこそこ作ったなあ、と思う。実感としては「やってみた」「つくってみた」は凄い大事なんだけど、さらにちゃんと自分以外の人が使えるレベルまで自分なりに仕上げてリリースする、っていうのは本当に力になった気がする。ツライけど。

やってみたレベルだとローカルどまりでデプロイとか運用とかは考えなくていいので、そもそも設計も違ってきたりする。それに「人が使える」というレベルは自分が想像するよりけっこう遠い。自分はアイデアの発案だしUIもロジックも実装して、概念も把握してるから説明無しで使えるけど、いざ始めて人が使うとすると全然配慮が足りなかったりUIが悪かったりする。業務でやってると他の役割の人がカバーしてくれたりするけど独りプロダクトだと全部そういうとこまで見すえてやらなきゃいけない。

そういう苦労した経験が事実として今年後半になって仕事をするうえでもしっかり活きてきたなぁと実感している。

最近ブログコミュニティに入ってちゃんとブログをこうして書くようにしてるけど、作ってみたものはちゃんとリリースすると、文章のアウトプットにも匹敵するか、もしくはそれを遥かに凌駕するリターンがあるので本当にオススメです。ツライけど。

エンジニアになるときの他の会社の同期とか、今の会社の同僚とかを見てもブログとかにアウトプットしてる層もそもそも多くないけど、趣味でもリリースまでちゃんとやりきったアウトプットしてる人って本当にごく少数。それにはエンジニアリング力だけじゃなくてアイデアとか他の力も要求されるので簡単ではないんだけど、それにしても少数。

やっぱりユーザーが居たり使ってくれて「いいね!」ってくれる人がでてくると本当に嬉しいです。今も1つ作りかけてとりあえず実用できないこともないギリギリレベルまで来たので、来年もいろいろリリースまでちゃんとやるのを意識しつつ、さらにそこで得た知見を文章でもアウトプットしていきたい所存。

Firestoreを使うためにVuexFireを使ってちょっぴり悩んだ話

  1. 1. 入れかた
  2. 2. VuexFireのRootのMutationのみ対応
  3. 3. ライフサイクルフックはCreatedで

ちょっと業務でFirestore使ってみようかーみたいな話がでたもんで、おっじゃあちょっと使ってみようと思いまして。で実際使ってみたらアイデアが湧いてきたのでFirestoreを使った趣味Webアプリを作ってみてます。

その過程でVuexFireというライブラリを使ってみてちょっと苦戦したけど無事できた顛末

入れかた

まずVuexFire。

ここで注意したいのはVuexFireという名前だけど、場所はGitHubのvuejs/vuefireにあること。
もともとはVuexFireだったのがVueファミリーに組みこまれたのかな?

それで、注意すべきは入れかたは

npm install vuexfire@next --save
# or yarn add -D vuexfire@next

普通に入れるんじゃなくて@nextが必要なこと。

今の環境に対応するにはalpha版の@nextで入れないと使えないことに注意。
まずこれをちゃんと読まずに入れたら上手く動かずにウンウン唸ったあげく1時間ぐらい飛ばした。無念
。いつもどおりだなーとわかったふりじゃなくてちゃんとREADME読め案件ですね。

VuexFireのRootのMutationのみ対応

そもそもVuexFireはどんなライブラリかっていうとVuexのMutationでデータの変更先をFirestoreにて、Firestore経由でVuexのStoreを扱えするやつ。従来Action経由でMutationをコミットしてStoreを変更するのがMutation部分がまるっとVuexFireになるので、Actionから変更するようなイメージに近くなる。

で、Vuexの細かい話は割愛するけども、Vuexがそこそこの大きさになるとnamespaceで分割していくと思う。でもVuexFireではMutation直で読み込むため、Rootで設定しないとちゃんと動かないみたい。
良くみるとちゃんのREADMEにも書いてある。

VueFire Usage

良く探してみたらこの件についてちゃんと言及してるQiita記事があったので詳細はそこに譲りたいと思います。言及先でもあるようにちゃんとREADME読め案件ですね、読んだつもりじゃなくてちゃんと読むの重要。

ライフサイクルフックはCreatedで

Vue.jsで読み込み時に最初にデータをロードしたい場合、beforeCreateとかで読みこんだりするのが定石だと思う。特にaxiosなんかで外部から持ってくる場合はみんなそうやってると思う。

Nuxtなんかだと、fetchとかasyncDataが備わってるからあたりまえのようにやってるんじゃないかと思う。だからこう、いつもの癖で

export default class extends Vue {
fetch({params}) {
const ref = params //雑な例
this.$store.dispatch('setUsersRef', ref)
}
}

やっちゃってた。

これでウーンおかしいなぁ、データがロードされないぞーウーンウーン、あーでもない、こーでもないってやってたわけで、慣れてないのもあって試行錯誤してみたんだけど、全然そうじゃなくてちゃんとIssueに質問として挙がってた。

というわけで

export default class extends Vue {
async created() {
const ref = params //雑な例
await this.$store.dispatch('setUsersRef', ref)
}
}

createdでフックしなきゃいけないっぽいです。これに気づかずに2時間ぐらいは飛ばした。

ただ、最近のIssueを見てみるともっと早いタイミングでbindする方法も模索してるみたいで、ちょっと期待。とにかく今はcreatedでやるしかない。

なので、絶対にデータがある前提でもcreatedフックでbindしても実際のデータがロードされるのは表示領域が発生してからということもあると思うのでそこでエラーにならないように実装する必要もある。

これで使えるようになったけど、じゃあ実際にアプリを動かしてみるとVuexFireをメインで使っていくのがいいのかはまだちょっと不明。とくにロードのパフォーマンスというかドキュメントの取得回数的に。そのへんのことはまたそのうち書くと思うけど、firestore面白いけど、反面Productionレベルでの知見がまだまだ出てきてない(そりゃBetaだから当たり前かもだけど)。

使ってみた、はたくさんあるけど、デプロイ以降運用やパフォーマンス、設計のベストプラクティス、金銭的に効率の良い使い方とかプロジェクトの扱うドキュメントの数にもよるけど考えることは無数にある感じ。なので「使ってみた」に留まらずちゃんと人が使えるレベルまで落としこんで知見をどんどん身につけていきたい所存。

AtomからVSCodeに乗り換えたので使ってる拡張パッケージを対応表にしてみた

  1. 1. 使用感比較
    1. 1.1. UI
    2. 1.2. 見た目のカスタマイズ
    3. 1.3. パフォーマンス
  2. 2. 選択系、変換、入力補助(エディタ操作)
  3. 3. ファイル、タブ、ペイン操作系
  4. 4. HyperClick系
  5. 5. ミニマップ
  6. 6. その他、機能拡張
  7. 7. Linter系(Atomのみ)
  8. 8. 各言語とか用途とか別
    1. 8.1. Git
    2. 8.2. Markdown
    3. 8.3. JSON
    4. 8.4. Ruby
    5. 8.5. JavaScript系(Node.jsやメタ言語、フレームワーク含む)
    6. 8.6. HTML系(メタ言語含む)
    7. 8.7. CSS系(メタ言語含む)
    8. 8.8. PUML
    9. 8.9. その他の言語系
  9. 9. おしまいに

年末年始にAtomの使ってるプラグインを列挙して棚卸しをしたけども、ちょっと前のMSのGitHubの買収を機に食わず嫌いしてたVSCodeを使ってみた。

ただし使うにあたってはAtomで使ってる環境と同程度のことができてくれないとダメなので調べてみた。

結果から言って今はVSCodeに乗り換えてしまった。せっかくなので使ってるAtomとVSCodeの拡張パッケージをそれぞれ対応する表にしてみた。ちなみに元々「俺のAtomは環境だ」と笑い話的に言っていたのでややAtom寄りの表になってるのはご容赦いただきたい。

使用感比較

拡張パッケージ表に行くまえに使用感を比較した所感。100%満足はしてないもののパフォーマンスの恩恵が大きく、なんとかAtomでできたことを損なうことなくVSCodeでも許容できるレベルにもっていけたのが乗り換えの決め手。

UI

ほとんどどちらも同じような感じで差異は大きくない。ただ、プロジェクト内から文字列検索した時の結果表示がAtomのほうがエディタ部に表示されるのでわかりやすい。VSCodeだとエクスプローラー部分に表示されるのが小さくてみづらい。

またキーバインドの設定はAtomイベントが発火したのか表示できるけど、VSCodeは表示する機構がないで、コンフリクトしてるキーバインドのデバッグが非常にしづらい。

見た目のカスタマイズ

見ためのカスタマイズは完全にAtomのほうが柔軟。これはDevToolsを開ける上、設定のLESSファイルを自由に編集できる。ということは変更したい箇所のセレクタにCSSを書いてあげることで如何様にもなる。

一方VSCodeは全てではないものの主要箇所はできて、まあ必要十分かな、という感じではある。個人的にはツリービュー(エクスプローラー)の文字サイズや行の高さなどはカスタムしたいけどできないのがもどかしい。

パフォーマンス

よくAtomは遅いと言われるけど、うん、まあ否定できない。それでも一応速くはなったんだけど。あとログファイルみたいに大きなファイルを開くとビーチボールがぐるぐるしてどうしようもなくなることがある。

VSCodeは最初の表示は速い。ウインドウの起動はちょっとかかるけどAtomに比べたら軽い。またファイル表示はキビキビ開く。ただし、これはどうもLinterやシンタックスハイライトなんかは開いた後でやってるような感じ。まず表示を優先する、みたいな挙動っぽい。大きなファイルは大きすぎた時はまず警告出してくれるので助かる。


さて、以下に拡張機能を列挙していくんだけど、自分で探す場合の注意としてはVSCodeのパッケージは検索すると同名のものが出てきたりする。同名のものをフォークして上げただけ、みたいな感じっぽいけどインストールの際はちょっと注意したい。

また、見つからなかったものは[-]とかって書いてあるけど、単に検索力が足りなかったり別のパッケージの一部にその代替機能が含まれてたりする可能性はあるのでご注意されたし。

選択系、変換、入力補助(エディタ操作)

概要 詳細 Atom Package VSCode Package
矩形選択 SublimeText的な矩形選択。見たままの四角の範囲を選択可能。 Sublime-Style-Column-Selection -
選択範囲を段階ごとに広げる 選択範囲を文字、単語、クォートや括弧で囲んだ要素、と順々に大きくできる。 expand-region expand-region
キャレット列のハイライト - highlight-column -
キャレット行のハイライト - highlight-line ビルトイン
選択した文字列のハイライト 選択してるものと同じ文字列をハイライト。 highlight-selected -
ブラケットハイライト 対応するブラケットをハイライト ビルトイン -
ブラケットカラーハイライト 対応するブラケットごとに色をかえてわかりやすく表示 bracket-colorizer Bracket Pair Colorizer
文字ケースの相互変換 キャメルケースとスネークケースだけでなく、ケバブケースもドット記法なども幅広く対応してるのでうれしい。 change-case change-case
クォーテーションマークを相互に変換 シングルクォートとダブルクォートのトグルだけでなく、設定でバッククォートも対応できる。 toggle-quotes Toggle Quotes
二元的要素のトグル変換 true/falseなどをトグルで変換。設定で対応文字列をカスタマイズ可能。 toggler toggler
タブ-スペース変換 インデントのタブorスペースをトグル変換。 tabs-to-spaces ビルトイン
セミコロンやカンマ付与 今キャレットがどの列にあろうとも現在の行末にセミコロンとカンマをつける。キーバインドで設定すると捗る。 trailing-semicolon toggle semicolon
行末スペース制御 行末のスペースを目立たせたり自動で削除したり。Atomはスタイルをカスタムすると良い感じ trailing-spaces Trailing Spaces
全角スペース制御 全角スペースを強調表示。Atomはスタイルをカスタムすると良い感じ。 show-ideographic-space EvilInspector
制御文字の制御 BackSpaceなど制御文字を削除するフォーマッタ。不意に紛れこむと思わぬバグを起こすのでありがたい。 - Remove backspace control character
連番の数字を挿入 複数行にわたって一括で連番生成。 sequential-number vscode-input-sequence
選択した複数行をソート 選択した範囲に含まれる複数行をアルファベット順や逆順などでソート lines Sort lines
カラーコードの部分だけカラー表示 カラーコードになってる文字列の背景色をその色で表示。CSSとか書くときに便利。StylusやSassの変数も対応してるので重宝する。 pigments Color Highlight
正規表現補助 正規表現をビジュアルで表示してくれる。 regex-railroad-diagram Regex Railroad Diagrams
Path入力補完 Path入力補完、でもたまに邪魔なときがある気がする。 autocomplete-paths Path Intellisense
列のフォーマッタ オブジェクトの定義とか、連続して変数に値を入れるとき、=:をセパレータとして左右のインデントが揃うようにしてくれるフォーマッタ。言語別のものもある。 aligner Better Align
インデントフォーマット ネストした要素のインデントをうまいこと整形してくれるフォーマッタ。JSONとかも良い感じにやってくれたり。けっこういろいろ対応してる。 atom-beautify Beautify
高機能マルチカーソル デフォルトより高機能なマルチカーソル。キーバインドがバッティングしやすいので要カスタム。 multi-cursor-plus -
キャレットの行移動補助 設定した行数文だけキャレットを一気に移動する。Emacsのページ送り的な送り用途として使える。 line-jumper line-jumper

ファイル、タブ、ペイン操作系

概要 詳細 Atom Package VSCode Package
ペイン自動最大化 複数Paneで分割してる時にアクティブなペインを自動的にほぼ最大化する。フォーカスしたペインを自動的に最大化したりもできる。 hey-pane -
ツリービューのフィルタ ツリービューで表示しているファイルをインクリメンタルに絞り込む。 tree-view-filter -
タブの規制 最大タブ数の設定制御。設定数を越えると古いタブから自動的に閉じて入れ変わる挙動になる。設定でピン止めしたファイルや、未コミットファイル除外したりもできる。たくさんファイル開きすぎてわかんなくなっちゃうので。 zentabs -
メソッド定義などのツリー表示 定義されたメソッド、変数などをサイドドロワーにツリー表示 symbols-tree-view ビルトイン
TODOコメントの抽出 プロジェクト内に存在するTODOやNOTEなどのコメントを抽出して表示。 todo-show Todo Tree
GHQ連携 CLIリポジトリ管理ツールghqの管理下にあるプロジェクトのショートカット。全てのプロジェクトがGit管理されていれば、プロジェクトマネージャーはこれだけで十分だと思う。 douglas vscode-ghq
変更ファイルの強調 ツリービュー(エクスプローラー)上でGitステータス▽による色分け表示する。追加ファイルや変更したファイルとかが視覚的にわかりやすいし、未コミットのファイルもみつけやすい。 tree-view-git-status ビルトイン
ファイルエンコーディング判別 エンコーディングの自動判別。 auto-encoding ビルトイン
ファイルエンコーディング変換 マルチバイトのファイルをutf-8に変換。 convert-to-utf8 -
FuzzyGrepファイル検索 プロジェクト内をag的なfuzzyGrepしてファイル表示。 atom-fuzzy-grep -
ディレクトリごとのファイル操作 ディレクトリごとに絞り込みでファイルを開ける、フルキーボードで階層ごとに掘っていく場合に便利。 advanced-open-file -
Expose風タブ操作 開いてるタブをmacのexpose的に表示、切り替えできるやつ。 expose -

HyperClick系

概要 詳細 Atom Package VSCode Package
HyperClick コード中のいろんな要素がクリッカブルになる。キーバインドにも対応していて対象の文字列にキャレットがあるときはキー操作でも動作する。**-hyperclick系のアドオンとかで拡張可能。言語特化のアドオンは後述の言語別のところで。 hyperclick -
HyperClickのURL対応 URLをクリッカブルにしてデフォルトブラウザで開けるようになる。 hyperlink-hyperclick ビルトイン

ミニマップ

概要 詳細 Atom Package VSCode Package
Minimap サイドドロワーにコード全体をざっと単純化して見わたせるようなやつを表示。他のパッケージで拡張可能。 minimap ビルトイン
Minimap選択文字列ハイライト Minimap内で選択した文字を全てハイライト。 minimap-highlight-selected select highlight in minimap
Minimapを自動で隠す ミニマップをスクロールしてないとき以外は自動的に非表示 minimap-autohider -
Mimimap内検索文字列ハイライト ミニマップ内で検索文字列を全てハイライト。 minimap-find-and-replace ビルトイン
Minimapカラーコード表示 ミニマップ内でカラーコード部分をカラー表示。 minimap-pigments -

その他、機能拡張

概要 詳細 Atom Package VSCode Package
多人数同時編集 他の人とつなげて同時にシェアしながらコーディングできる。モブプロ、ペアプロなどでものすごい活躍する。 teletype VS Live Share
Atomキーマップコンフィグ Atomでデフォルトのキーマップに一括で変更する ビルトイン Atom Keymap
コーディングフォーマットの統一 EditorConfigの対応。プロジェクトルートに.editorconfigという設定ファイルを置いて制御。 editorconfig EditorConfig for VS Code
設定の共有、同期 GitHubGist経由でエディタの設定、インストールしているアドオン情報を複数端末で同期する。 sync-settings Settings Sync
ノートテイキング 検索しやすいノートシステムをエディタに組み込み、メモやスニペットなどの一元管理を自分のカスタマイズしたエディタで編集できるのは強み。Atom版はnotational-velocityライクでかなり使いやすい。 atom-notes VSNotes
ブラウザ連携 同名のGoogleChrome拡張をブラウザに入れることで、ブラウザで開いてるページのテキストエリアを同期的にエディタで編集できるようになる。例えばPull RequestなどMarkdown対応のテキストエリアをエディタで編集できるのはかなり便利。 atomic-chrome GhostText
ターミナル エディタ内でターミナルを動作させる platformio-ide-terminal ビルトイン
CSVエディタ エディタからCSVを表計算アプリケーションのように表示、編集できる tablr Excel Viewer
拡張パッケージ管理 パッケージにアップデートがあったら自動でアップデートする。ちょいちょい自分で確認しなくていいので楽。 auto-update-packages ビルトイン
ファイルアイコン ツリービュー(エクスプローラー)やタブにファイルのアイコンを表示して視認性を上げる。VSCodeでは各種テーマでさらにカスタム可能。 file-icons ビルトイン
ファイルタイプの判別補助 自動で判別されるファイルタイプのルールをカスタマイズを楽にする。 file-types -
差分表示、補助 Paneで分割してDiff表示、見やすい。しかも保存してない状態でもDiffとれるので、長大な文字列とか大きめなオブジェクトを一時的に比較するときも重宝する。 split-diff Partial Diff
コードプリプロセス プリプロセッサ言語から元言語へ変換。 preview -
ステータスアイコン ステータスバーに状況表示アイコンをプラス。確かAtom用のLinterと一緒に入ってくるはず。 busy-signal -
ウィンドウタイトルのカスタマイズ Atomのウィンドウに表示されるタイトルのルールを変更できるようになる。上手く好きなようにファイル名の表示とかにカスタマイズすると地味に便利。 custom-title -
定義元にジャンプ メソッドの定義元にジャンプできるようになる。 goto-definition -
ステータスバーにファイル名表示 現在開いているファイル名とファイルパスをステータスバーに表示 ビルトイン Active File In StatusBar

Linter系(Atomのみ)

概要 詳細 Atom Package VSCode Package
Linter Linter機能。各言語用のPackageを別途追加が必要。各言語用のLintは後述。 linter -
Linter表示 Linter表示用UI。たぶんLinter入れると入ってくるはず。 linter-ui-default -

各言語とか用途とか別

Git

概要 詳細 Atom Package VSCode Package
Git総合サポート Git周りのこといろいろ - GitLens — Git supercharged
Git履歴表示 Git logビューア git-log Git History
Git変更内容アイコン AtomにビルトインのステータスバーにGitのファイル変更状況アイコンを表示する - Git Indicators
gitignoreサポート gitignore用のシンタックスハイライト - gitignore
Gist GitHubのGistを編集したりアップロードしたり、挿入したり。Gist系はいくつかあったけどこれが一番使いやすかった。 gist -
Git Blame表示 行ごとにGitで変更した人を表示する。 git-blame GitLens — Git supercharged
Git操作ショートカット よく使うGit操作をAtomから直でできる、Atomのコマンド補完が効くので便利、基本的なgit操作はこれだけでいける。ビルトインのものでも十分だがあると便利 git-plus -
コンフリクトのサポート Gitでmergeしようとしてコンフリクトしたときの編集サポート。 merge-conflicts ビルトイン
ホスティングサービス対応 GitHubなどのホスティングサービスを開いたり、プルリクエストページを開いたりできる。GitHubだけでなくBitbucketなど他のサービスにも対応しててうれしい - Open in GitHub, Bitbucket, Gitlab, VisualStudio.com

Markdown

概要 詳細 Atom Package VSCode Package
Markdown表示の拡張 デフォルトのMarkdownプレビューより高機能なプレビュー、TOCやプレゼンモードもあったりする。目次機能もあったり、いろいろと便利。 markdown-preview-enhanced Markdown Preview Enhanced
入力補助 Markdownの全般的な入力補助。とりあえず入れてる。 markdown-writer Markdown All in One
目次自動生成 編集中のMarkdownの目次を自動生成 Markdown TOC Markdown All in One
テーブル記法補助 Markdownのテーブル記法編集補助、うまいこと縦のカラム表示を整えてくれる。 markdown-table-editor Markdown All in One
テーブル記法補助2 別のMarkdownのテーブル記法編集補助 Markdown Table Formatter Markdown All in One
Markdown目次機能 ドロワーやエクスプローラーに編集中のMarkdownの目次をページ内リンク付きで表示。 document-outline ビルトイン
タスク記法のトグル Markdownのチェックボックス記法のチェック状態のトグル変換。そんなに使わないけどいちいちキャレットを移動するのが面倒なので。 toggle-markdown-task Markdown Checkboxes
フォーマッタ 番号付きリスト記法の番号振りなおしなど、フォーマッタ。 tidy-markdown -
シンタックスハイライト Atomデフォルトのものより高機能なハイライタ。 language-markdown -
TextLint 特に日本語に強い自然言語用のLinter、textlintをAtomで使えるように。Markdown以外でも効く。 linter-textlint vscode-textlint

JSON

概要 詳細 Atom Package VSCode Package
シンタックスハイライト(階層) JSONファイルを階層によってカラーリングを変えて視認性を上げる。 atom-json-color -
フォーマッタ JSONファイルの整形フォーマッタ。 pretty-json JSON Tools
Linter JSON用のLinterアドオン。 linter-jsonlint -
ソート JSONオブジェクトをアルファベット順などでソートできる。 - Sort JSON objects

Ruby

概要 詳細 Atom Package VSCode Package
総合サポート - Ruby Ruby
Hamlサポート haml用の言語ファイル。 language-haml Ruby Haml
Slimサポート Slim用の言語ファイル。 language-slim Slim
Rspecサポート Rspec(Rubyのテストフレームワーク)用の言語ファイル。 language-rspec rspec-snippets
Rablサポート Rabl(RubyOnRails用のJSONやxmlに特化したテンプレートサポートGem)用の言語ファイル。 language-rabl -
Linter(RuboCop) Rubocop用のLinterアドオン。 linter-rubocop ruby-rubocop
Linter(erb) erb用Linter linter-erb -
Linter(Haml) Haml用Linter linter-haml -
Linter(Slim) Slim用Linter linter-slim -
入力補完 Ruby用の入力補完。別途Solagraph Gemのインストールが必要。 autocomplete-ruby Ruby Solargraph
自動修正 Rubocopルールに従って自動修正。 rubocop-auto-correct -
フォーマッタ Ruby用フォーマッタのrufoをエディタから実行。JSのprettier用のプラグインのほうが主流になりそうな感じ。 rufo-atom rufo (Ruby formatter)
Railsサポート - - Ruby on Rails
Railsパーシャルビューサポート Railsのパーシャルビューの自動補完 autocomplete-rails-partial -
Rspecファイルを開く 現在開いてるファイルに対応したRspecのファイルを開く。 rails-open-rspec -
Rspecの実行 エディタからRspecをrun。 rspec Rails Run Specs
ブロック文法の補助 do,if,begenendなどの対になるブロック要素をハイライト。 ruby-block -
i18nサポート Railsのi18n用のAutocompleteとHyperClickアドオンのセット rails-i18n-plus -
Rails補助 Rails用のスニペット集。 rails-snippets Ruby on Rails
Rails補助(ファイル) Model,View,Controllerなど対応するファイル同士を素早く開けるようにする。 rails-transporter -

JavaScript系(Node.jsやメタ言語、フレームワーク含む)

ちなみにPrettier系入れずにプロジェクトにESlintと統合するeslint-plugin-pretteirを使って統合している。

概要 詳細 Atom Package VSCode Package
TypeScriptサポート TypeScriptの言語ファイルか補完や便利機能まで、IDE的サポート。 atom-typescript ビルトイン
Vue.jsサポート Vue.js用の言語ファイル。 language-vue Vetur
Vuc.js入力補完 Vue.js用Autocompleteアドオン。 vue2-autocomplete Vetur
ESlint JavaScript用のLinterであるEslintサポート、prettierと連携も可。 linter-eslint ESLint
JS用HyperClick JavaScript用のHyperClickアドオン。 js-hyperclick -
Vue.js用HyperClickアドオン Vue.js用HyperClickアドオン。 vue-hyperclick -
Jestサポート JS用テストフレームワークJestのサポート - Jest

HTML系(メタ言語含む)

概要 詳細 Atom Package VSCode Package
Pugサポート - language-pug Pug (Jade) snippets
HTML用Linter HTML用Linterアドオン。 linter-htmlhint -
Pug用Linter Pug用Linterアドオン。 linter-pug -
HTMLタグ補完 HTMLの閉じタグのショートカットと補完。Pugで書けばいらないんだけどね。 tag Auto Close Tag
Emmetサポート html,css(プリプロセッサ含む)の強力なスニペット集。 emmet ビルトイン
インデント記法サポート Jade(pug)やStylus,Sassのインデントベース記法の環境で、現在のキャレットの位置がどの要素のネスト中なのかツールチップで表示。 indent-tooltip -

CSS系(メタ言語含む)

概要 詳細 Atom Package VSCode Package
Stylusサポート Stylus用の言語ファイルとスニペット集。 Stylus language-stylus
Stylus自動補完 Stylus用のAutocompleteアドオン。 autocomplete-css-with-stylus-support -
Stylusフォーマッタ Stylus用フォーマッタSupremacyサポート、かなり柔軟な設定が可能。 - Manta’s Stylus Supremacy
Stylus用Linter Stylus用のLinterアドオン。 linter-stylint Stylint
CSS用Linter CSS用のLinterアドオン。 linter-stylelint stylelint
Sass用Linter SCSS(SASS含む)のLinterアドオン。 linter-scss-lint Sass Lint

PUML

UMLをテキストから表現したもの。
細かいレイアウトは難しいもののテキストならGit管理もできるし素早くかけるので覚えてよかった。

概要 詳細 Atom Package VSCode Package
PlantUMLサポート PlantUMLの言語ファイル language-plantuml PlantUML
PlantUMLビューア PlantUML用のUMLを表示 plantuml-viewer PlantUML

その他の言語系

概要 詳細 Atom Package VSCode Package
Apache Apacheのコンフィグ用 language-apache Apache Conf
nginx nginxのコンフィグ用 language-nginx nginx.conf
Lisp Lispサポート language-lisp Lisp
envファイル 環境変数を設定するenvファイルのシンタックスハイライト DotENV -

おしまいに

VSCodeに乗り換えたものの、今もAtomは好きだし、なんとなくVSCodeは好きになれない。もしかしたらあるイベントで好きでAtom使ってるところにかなり無理にVSCodeを勧められた経験からかもしれない。(やれ補完がどうとか。それはVSCodeの機能ではなくLanguage Serverだ)

Web上でもVSCode推しみたいのを良く見る。しかもAtomを貶めてまで。エディタは宗派で戦争になるためそういう推し方は好きじゃないんだよなぁ。あ、あれだインド行った時、自分の旅の一部としてとても楽しかったけど、インド推ししてる日本人の方々をどうも好きになれなかった感じと似ている。

なんにせよどんなエディタを使おうにもこういう拡張が充実してきてみんなが平和に好きなエディタを心地良く使う世の中がいいよね。

ちゃんとしたGitコミットメッセージをCommitzenを日本語で使って楽に書く

  1. 1. 決定打は’Commitzen’
  2. 2. タイプ、スコープの効能
  3. 3. 日本語でどうしましょ?
  4. 4. Commitzenを少しだけ日本人に優しくしました
    1. 4.1. 使い方
  5. 5. もうちょっと拡張するには
    1. 5.1. 使い方

Gitを使うようになって以来、使えば使うほどこれは良いバージョン管理だなぁと関心する。その反面、コミットに対しての悩みはつきない。コミットメッセージ、粒度。そのへんをどうしたら良いのか決定打が無いまま、ちゃんとしてるっぽいようにできてはいるけど今イチ自信がないままだった。

そう思いつつもいろいろと思考錯誤してようやく最近ではこれで行こう! と自信もって上手くできてる感がでてきたのでそのへんのことを共有していこう。

決定打は’Commitzen’

Commitzenは一言で言えば対話的にコミットメッセージを作るやつ。NPMで配布されていて、Angularで使われているコミットメッセージのルールが元になってるそうな。

簡単に言えば

Type(Scope): Title

Body

というメッセージルールで書く。ScopeBodyに関してはオプショナルでなくても可。
タイプは例えば、feat, fix, style とか。大項目みたいな。
スコープは言わずもがな。変更範囲。タイトルは普通のコミットメッセージみたいなコミットの要約で、Bodyはさらに細かい説明、これはよくある1行空けて詳細を説明、みたいなのと一緒。
これを対話的に

  1. このコミットのタイプは? (選択式)
  2. スコープは?(enterでスキップ)
  3. コミットの要約
  4. さらに細かい説明(オプショナル)
  5. 破壊的変更について
  6. 関連するissueについて

みたいな感じで質問に答えるように入力する。こうすることによって「うーん、コミットメッセージ、どう書こう」みたいなのをちょっと楽にしてくれる。さらには、タイプを選択式にすることによってメッセージの統一性を強制し、スコープをちゃんと考える契機にもなる。

タイプ、スコープの効能

実はメッセージを入力するのが楽になるだけじゃなくて「メッセージと内容の整合性」をちゃんと意識してる場合、変更内容の粒度や区切りもある程度しっかりしてくる。

バグ修正のコミットに機能追加を含めてはいけない。後からアレコレしたい場合に無理が生じてくる。Gitの良いところは履歴であり戻れることなので、戻りやすく、選択できるレベルにしておくのが良い。だけど例えばバグ修正と機能追加が1つのコミットにある場合、バグ修正は取り込みたいけど、機能追加は問題があって取りこみたくない、という場合に死ぬ。

さらにconventional-changelogというのがcommitzenプロジェクトの一環にある。これはこのタイプを自動で判別してCHANGELOG.mdを自動生成したり、セマンティックバージョニングをコントロールする方法。簡単に言えばfixが含まれていれば0.0.1(patch)上がってfeatが含まれていれば0.1.0(minor)上がる。

日本語でどうしましょ?

僕は個人的に言えばプライベートなものかつ、日本人のみのチームで作業されるものは日本語コミットメッセージで良いと思ってる。コミットメッセージを書くためにうんうん悩んで時間を浪費したりするのは本質じゃないし、読むときもいちいち機械翻訳にかけて理解するのももったいない。

ただし、被検索性は高くもっていたいのでそれなりなルールは必要で、コミットメッセージに日本語か嫌われるのはこのへんが大きいとにらんでる。問題は文法と表記のあいまいさ。

英語だとSVC文法で、さらに本質から先に出てくる。日本語は逆でSCVというか、大切なことほど後にでてくる(ごめんなさい、このへんの言語的な細かいことは正確に解説できる知識がないです)

fix SomeClass work properly

というメッセージと

SomeClassが正しく動作するように修正

というメッセージ、どっちが見やすい? 英語のほうが理解しやすいように思う。後からコミットメッセージをもとに探す場合、もう何でさがしたらいいかわからない。バグフィクスって書く? 修正って書く? っていう表記揺れだったり、「修正」が最後に来るので目で追う場合もズレる。文字数が多くなって自動でBodyに送られた場合はさらにキツい。

で、じゃあ表記揺れをルールで縛ってある程度書き方も一文じゃなくて配慮したルールにしてみたとしよう(これは以前僕が日本語コミットを書くなら、と独自に考えてみたもの)

修正: SomeClassが正しく動作するように

もしくは「修正」を外に出したので説明を追加すると

修正: SomeClassが正しく動作するようにsome-functionを追加

とか。これならまず本質が「修正」であることがわかるし、メッセージの最初が主語なのでSomeClassに対する修正だな、とわかりやすいと思う。そしてこれをAngularルールで書いてさらに日本語を混ぜると

fix(SomeClass): add some-function for working properly

fix(SomeClass): 正しく動作するようにsome-functionを追加

日本語話者にとってもタイプとスコープ程度は英語でも誰も困らないし、ちゃんと明記されてるし、その後の説明は日本語でも何をしたのかわかるし。まずタイプとスコープを最初に固定することでそのあとメッセージがもうちょっと変更内容をちゃんと説明しやすく書ける。

これだと日本語コミットでもわかりやすいし、バランスも良いと思う。もちろんパブリックなリポジトリや日本語話者のみで構成されていなければ英語で書くべきなのは言うまでもないけど。

Commitzenを少しだけ日本人に優しくしました

そんなわけで、日本語で書こうが英語で書こうがCommitzenが役に立つのはわかってもらえたはず。で、日本語で書くような場合、対話型で設定できたとしてもまだちょっとやりづらい。英語にそこまで不自由を感じないようになった僕でも日本語を読むほうが圧倒的に速い(漢字は文字あたりの情報量が圧縮されているということ抜きにしても)。

で、commitzen(cz-cli)は何もしないと対話的CLIのところにcz-conventional-changelogが使われている。ここを~/.czrcとかで別のものに指定ができるような作りなのでcz-conventional-changelog-jaというもの作った。作ったと言ってもオリジナルをフォークして日本語訳しただけなのですがね。

これを適用すると対話の質問やタイプ選択の説明が日本語になる。自分でも使ってみたけど、英語でコミットするにしてもこっちのほうが使いやすい。

使い方

グローバルに設定してプロジェクト問わず使うとしたら

$ yarn global add cz-cli cz-conventional-changelog-ja
# or npm i -g cz-cli cz-conventional-changelog-ja

とインストールしたら、ユーザー直下に

~/.czrc
{
"path": "cz-conventional-changelog-ja"
}

としてやる。そうすると

$ git cz

とやったときにデフォルトのcz-conventional-changelogではなくcz-conventional-changelog-jaを参照するので、日本語で表示されるようになるはず。

もうちょっと拡張するには

それで使ってたんだけどどうも僕がメッセージ書いててタイプの種類が少なかったり合わなかったり感じてた。コミットメッセージを書きやすくするためのツールなのにこの場合のタイプはうーんどうしよう、みたいに詰まるのは本末転倒だなぁ、と。

もちろんこのTypesを制御する方法ああるんだけど、これってプロジェクトやチームによっても変わるのでもっと柔軟なほうが良さそう。その都度別バージョンのcz-conventional-changelogをフォークしたりするのも違うよなぁって思いもあってcz-customizable経由でやることにした。

cz-customizableは、質問やタイプ情報の設定を外部から読み込めるようにして、カスタムできるようにしたもの。なので日本語で使う場合も.cz-config.jsを書いて参照するようにしたらいい。

参考までに僕の設定を載せておきます。

使い方

運用としては、上の-jaのようにグローバルに設定してプロジェクト問わず使うとしたら

$ yarn global add cz-cli cz-customizable
# or npm i -g cz-cli cz-customizable

とインストールしたら、ユーザー直下に

~/.czrc
{
"path": "cz-customizable"
}

cz-cliが参照するのをcz-customizableにする。で、cz-customizableはデフォルトでは.cz-config.jsを参照するので、下記のようにファイルを作って置く、と。

~/.cz-config.js
'use strict';
module.exports = {
types: [
{
value: 'feat',
name: 'feat: 新機能',
title: 'Features'
},
{
value: 'fix',
name: 'fix: バグ修正',
title: 'Bug Fixes'
},
{
value: 'HOTFIX',
name: 'HOTFIX: 致命的で緊急なバグ修正',
title: 'Critical hotfix'
},
{
value: 'UI',
name: 'UI: UIやスタイルの更新',
title: 'UI'
},
{
value: 'docs',
name: 'docs: ドキュメントのみの変更',
title: 'Documentation'
},
{
value: 'style',
name: 'style: フォーマットの変更\n (コードの動作に影響しないスペース、フォーマット、セミコロンなどの変更)',
title: 'Styles'
},
{
value: 'texts',
name: 'texts: 文字や文章の更新',
title: 'Text and literals'
},
{
value: 'i18n',
name: 'i18n: 国際化',
title: 'Internationalization'
},
{
value: 'typo',
name: 'typo: タイプミスの修正',
title: 'Typos'
},
{
value: 'refactor',
name: 'refactor: リファクタリングのための変更\n (機能追加やバグ修正を含まない変更)',
title: 'Code Refactoring'
},
{
value: 'perf',
name: 'perf: パフォーマンスの改善のための変更',
title: 'Performance Improvements'
},
{
value: 'ux',
name: 'ux: ユーザーエクスペリエンス/ユーザビリティの改善',
title: 'UX'
},
{
value: 'test',
name: 'test: 不足テストの追加や既存テストの修正',
title: 'Tests'
},
{
value: 'config',
name: 'config: 設定の追加や変更',
title: 'Configuration'
},
{
value: 'build',
name: 'build: ビルドシステムや外部依存に関する変更\n (スコープ例: gulp, broccoli, npm)',
title: 'Builds'
},
{
value: 'ci',
name: 'ci: CI用の設定やスクリプトに関する変更\n (スコープ例:Travis, Circle, BrowserStack, SauceLabs)',
title: 'CI'
},
{
value: 'chore',
name: 'chore: その他の変更\n (補助ツール、ドキュメント生成などのソースやテストの変更を含まない変更)',
title: 'Chores'
},
{
value: 'WIP',
name: 'WIP: 作業中',
title: 'WIP'
}
],
scopes: [
// { name: '*' },
// { name: 'admin' },
// { name: 'exampleScope' },
// { name: 'changeMe' }
],
// it needs to match the value for field type. Eg.: 'fix'
/*
scopeOverrides: {
fix: [
{name: 'merge'},
{name: 'style'},
{name: 'e2eTest'},
{name: 'unitTest'}
]
},
*/
// override the messages, defaults are as follows
messages: {
type: 'コミットする変更タイプを選択:\n',
scope: '変更内容のスコープ(例:コンポーネントやファイル名)(optional):\n',
// used if allowCustomScopes is true
customScope: '変更内容のスコープ(例:コンポーネントやファイル名)(optional):\n',
subject: '変更内容を要約した本質的説明:\n',
body: '変更内容の詳細("|"で改行)(optional):\n',
breaking: '破壊的変更についての記述(optional):\n',
footer: '関連issueを追記 (例:"fix #123", "re #123")(optional):\n',
confirmCommit: 'このコミット内容でよろしいですか?'
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix']
};

もし参考にしていただけたら幸いです。