欲しいものを作れる強さの楽しさと難しさ

  1. 1. 趣味は2種類持ったほうがいい、と思う
    1. 1.1. 創作趣味と消費趣味
  2. 2. 今回作ったもの
    1. 2.1. 作った風景とかコンセプトとか

久々に革工芸しました。年単位でやってなかったのでだいぶ腕が鈍っていました。でもやっぱり創作趣味はあるといいな、というお話。ちなみに今回は財布を作ってみました。

趣味は2種類持ったほうがいい、と思う

創作趣味と消費趣味。創作趣味っていうのは、例えば今回のクラフトなり、DIYとか絵を書くとか。いわゆる何かを形にしたり生み出したり。プログラミングで趣味開発もこれに相当すると思います。
消費趣味っていうのは音楽を聴いたり、映像を観たり、ゲームをしたり。何か作られたものを味わう趣味。

そう常々思うんですが、以前どっかで趣味は三種類持つべきって話を聞いた覚えがありまして。たしかそこではもう一種類は楽器を演奏する趣味、と定義されてた覚えがあります。ですが楽器の演奏ってなかなか日本の集合住宅事情を考えると簡単ではない気がしています。それが逆にストレスになりかねないので僕は賛成しかねます。

創作趣味と消費趣味

創作趣味の良さって、欲しいものがあるときに手に入らないとか制限がないこと。自分で作ればいい。早い話「ぼくのかんがえたさいきょうの○○」が手に入る喜び。いかに使うべきではない言葉なので修正してくださいなものを生み出すかを考えてる時も楽しくワクワクしますし、それを作るときのモチベーションと集中力たるやすごいものがありますね。

そして何か目に見えたり、触って楽しめるものができてくる楽しさ、だんだん形になっていく楽しさ。さらに経験を積むことでどんどん上手くなっていく喜び。自分の欲しいものを自分で作って手に入れられる自由が持つ強さは、想像するよりもストレスフリーなことです。

一方、消費趣味だけだともちろん楽しいんですがふと虚しくなったりしませんか? こんなにゲームばっかりやってて、自分には何が残るんだろう?と思うことないですか?創作趣味は形にのこるし、経験値が上がるのでそういうことはあんまりなかったりします。

でもいつもそんなことに没頭できるわけではなくて、創作趣味は消費趣味に比べてエネルギーがいります。疲れてたりするととりかかるのがなかなか難しかったりします。アイデアがピンと閃かないとモチベーションが上がらなかったりします。創作趣味だけだと継続できる余裕がとれなくなったとき、キツくなってきます

そんなときこそ消費趣味の出番です。低エネルギー状態でも楽しめる。良い気分転換になります。だからこれはこれで重要なんです。消費趣味で得た発想が創作趣味に活きることもあるでしょう。

そんなこんなで僕は創作趣味と消費趣味、2種類の趣味を持っているのがいいなぁ、と思っています。ちなみに消費趣味すら難しくなってきたときは警戒体制に入ります。自分の状況を冷静にふりかえってこのままでイケるのか、幸せか、を考えるタイミングです。

今回作ったもの

今回は財布を作ってみました。今までも財布は何度かトライしていて、いずれも失敗作です。僕の場合、すでに型があるものを作るのではなく、自分がこういう機能、こういうコンセプトのものが欲しい! と思ってるものを作ります。というか、市場に存在しないから自分で作らざるを得ない感覚に近いです。

これは楽しいだけじゃなくなかなかツラく感じることもあって、一発で上手く満足の行くものができません。だいたい設計にミスがあります。でも作ったり、使ったりしてやっと初めてわかることばかりです。とはいえ上手くはいかないとわかっていて作るのはなかなかツライ。でも作らないとわからない。

作った風景とかコンセプトとか

今まで薄い財布を作ろうとしてるんですが、今回もです。ただし今回は長財布。長財布でも小さめに作ってみたらどうだろう、というチャレンジです。小銭入れはいつもボックス型です。これは使い勝手を考えると譲れません。

今回はプロトタイプとわりきっているので必要以上に細かい仕上げはせず、革もハギレ優先で使いました。なので色の組み合わせとかも度外視です。


こんな感じで設計しています。毎度毎度使い勝手に最優先で作っています。市販にないもの持てるのが自作の醍醐味なので型は毎回作ります。設計と実際は作ってみてわかることが多いので正直しんどいです。この工程でほとんどが決まってしまうのに最初の工程というのが難しいところです。


プロトタイプであっても床面はちゃんと処理します。できる限り小さく持てるようにという設計が多いので、ちゃんとやらないと設計的寸法が破綻します。余談ですが市販の簡易な革グッズはコバや床面を処理してないものが多いですね。


パーツに切り出しまでできたら後は楽しいパート。個人的にはここに至るまでの設計から切り出しまでが一番楽しくないパート。切り出してパーツを合わせてみると寸法がおかしかったり、設計が破綻してたり、発覚することも珍しくないです。


縫ってる時が一番楽しいです。無心になれます。ただパーツの組み合わせ順が複雑だったりするときはたまに縫いつける順番を間違えます。
ボタン類や金具系は適宜つけていきます。因みに革工芸は縫う前に穴を空ける必要があるのでけっこう大変です。楽しいですが。
余談ですが、Webフロントエンドが形になってきたときの感覚に近いなーと思いました。形ができてきて、できたところは部分的に機能しはじめるというか。使い勝手はUI設計だ、みたいな。


こんな感じになりました。
カード入れは多少余裕を持たせたつもりですが、作ってみたらギチギチで1つのスペースに2枚いれるのがやっとです。革の厚みや伸縮とか縫いの幅とかの影響ですね。これはもっと余裕持たせなければいけません。そうなると大きさにも影響がでるんですが。
札入れは開口部の角度を大きくとりすぎたのと高さをとらなさすぎたので、使いにくいほどガバっと開きます。落ちそうで危いですね。ボタンは余ってた材料の関係でジャンパーホックにしてみたんですが、固すぎました。サッと開かないので使いづらい。
小銭入れはボックス型にしたのはそこそこ成功なんですが、縫い合わせが難しく失敗しました。もうちょっと余裕を持たせた作りにすれば変なひずみもでなかったんだろうなあ。

ほかにも細かい失敗はいくつかあるんですが、予想どおり失敗でした。

ただ、この失敗は試金石とも言うか叩き台というか。
しばらく使って再設計したら再度トライしようかな、と思っていますがモチベーションの維持が難しいところですね。少しだけ革工芸の感覚が戻ってきたので、もっと簡単なものを作るのもいいかな。

むりやり技術の話の話に結びつけると、趣味開発にしても仕事にしてもやっぱり日頃の素振りや成功以外の試した経験がモノを言う場面も多いので、これからもどんどん手を動かしていこう、と思いました。

Work in progress is better than not to do it

  1. 1. 何がしたかったか
  2. 2. 未来の休みの日の自分に期待しすぎない、押し付けすぎない
  3. 3. 戦い方と意識を変えよう
  4. 4. Challenge-Every-Month
    1. 4.1. MY_ROADMAP
      1. 4.1.1. [0.37.8] - 2019-06

2年半勤めた会社を退職して、月曜日から新天地でのお仕事が始まります。で、今週は有給消化期間でまるまる休みだったんですが、自分が思ってたよりもやりたいことの進捗が芳しくなかったことの分析と気持ちの整理と、これからの取り組みのお話。

何がしたかったか

この一週間の休みの間、ぼんやりと思ってたタスクは

  • 革工芸の勘を取りもどしがてら財布のプロトタイプを作る
  • ブログをHexoからVuePressに移行する
  • 使ってない銀行口座とかちゃんと整理する(平日自由に動けるので)
  • 服とか鞄とか日用装備を確認して必要なら刷新する
  • 今後のライフイベントに供えて必要な知識を勉強する
  • 中途半端になってるToDo管理、自分ナレッジ整理、家庭ハック
  • 筋トレ+ストレッチをじっくりと再始動

みたいな感じでした。休みが終わりにさしかかった今、ふりかえってみるとどれもちょっとは手をつけたものの完全に達成したものはないです。無念。

原因はやっぱりちょっとタスクをつめこみすぎたんでしょうね。あとはどれも自分の想定してたよりも時間がかかっている気がします。 ちょっと手をつけてみたら想定してたよりも時間がかかることが多かったです。

未来の休みの日の自分に期待しすぎない、押し付けすぎない

「あー休みに入ったら、あれやりたいな、これ片づけたいな」って過ごしていて、いざ休みの期間に入ると思いの外やりたいことが片づかず、やらなきゃいけないことも終わらず、無情にも休みが終わろうとしてしまうときの不完全燃焼な気持ち。

原因として思いあたるのは、休みの日の自分に仕事を押しつけすぎてしまっていること。期待しすぎてしまっていること。

単純に全部自分の自由時間として使えるわけじゃなく、平日にしなかったぶんの家事や雑務にも勤しまなきゃいけない。仕事みたいに8時間集中して作業できるわけじゃない。仕事ほど工数意識もシビアでなければ、見積りもちゃんとしてない、何かを試したりが多いのでそもそも見積れないようなことも多い。

こういうのが重なって「これぐらいは達成したい」を上手く達成できない間に休みが終わってしまうような気がします。そもそも目標設定が破綻してた感。

戦い方と意識を変えよう

そこでようやく今回のタイトル「Work in progress is better than not to do it」です。つまり「作業中状態であっても、なにもしないよりはマシ」な感じです。

以前はやっぱり完了状態にならないともやもやした感が晴れないなぁと思ってました。でも、そういって立ち止まってるよりはよっぽど良いかな、と。

ザッカーバーグさんの言う

Done is better than perfect.
(完璧にするよりまず終わらせよう)

のひとつ前段階なイメージです。

完璧じゃなくていいからやることを整理、取捨選択してまずは手をつけていこうという作戦。手をつけられたならまずは良しとしようという心構えです。少なくとも前には進んでるはず。今回で言えば、どれもまったく手がつけれなかったというものはなく達成度が100%にならなかったものがほとんど。

今までブログを書くのもサクサクできない理由も記事の完成度を気にしすぎている傾向にあるので、これからはもっと作業途中でもそこで区切れるところは区切ってアウトプットしていきたい。

今後、転職に加え来たるビッグなライフイベントにあたって自分のペースが保てなくなってくると踏んでいます。他の人と比較しながら競い合っても無理が生じそうな気がしています。だからまずは自分の目標を小さく短く作りつつ各個撃破、それが無理ならせめて手をつけていくやりかたにしようと。

小さくても良いから確実な一歩を積み重ねていこう。

Challenge-Every-Month

Write-Blog-Every-Weekでもお世話になってるよしたくさんが、月次でチャレンジ目標を立ててがんばっていこうよという趣旨のコミュニティのメンバー募集をしてたので前から気になったので仲間に加えていただきました。

それもあって、上で書いたようにやりたかったけど終わらなかったこと、終わらなかった理由とかを加味して、今月(6月)の目標を立ててみました、こんな感じ。

MY_ROADMAP

今月から月次目標をロードマップとして明文化していく作戦を展開する!

[0.37.8] - 2019-06

  1. ブログをHexoからVuePressに移行
  2. 5月の終わりから始めた自重トレORストレッチのどちらか、または両方を最低3回/週ペースを維持
  3. ブログ1記事/週を維持
  4. 久々に革工芸で財布を制作して勘を戻す

ブログはずっとなんとかしたいと思ってたのでこの際、VuePressに移行します。Markdownの中でVueコンポーネントが使えるので自分で拡張がしやすそうなのが良さそう。今は外部リンクとかAmazonのリンクとかが使いにくいことが少なからず記事を書く障壁になっているので、解消されれば心理的にも書きやすくなる効果を期待。

いつでも動ける健康的な肉体を維持するために、ちゃんとやる。前までは筋肉筋肉! という感じだったものをメンテナンス方向に比重を置いて継続を第一としてやっていく方向転換とその維持。1度の最大効果を上げるより継続することの効果を大事に。

既にブログ移行は8割ぐらいまでできてるし、筋トレとストレッチもここ一週間ちゃんと継続できてる。革工芸も久々で腕が鈍りまくってるけど、少しづつ進めてる。まずはこのあたりをちゃんと形にしていこうと思っています。

というのも6月頭から会社が変わるので、まずは環境の変化に慣れることに最優先であんまり詰めこみすぎないように注意してやっていきます。

VuePressを使ってみた所感

  1. 1. VuePress
    1. 1.1. 所感
  2. 2. 他と比較してみる
    1. 2.1. Hexo
    2. 2.2. Nuxt.js
    3. 2.3. Gatsby.js
  3. 3. まとめとこれからの方針

先週、毎週最低1記事を書くチャレンジを再スタートさせたんですが、どうせだからブログシステム自体をリニューアルしようと考えています。そこでVuePressはどうかな? と思って使ってみた所感。

VuePress

VuePress 1.x

Vue.jsベースの静的サイトジェネレータ。Vue.jsの開発者であるEvanさん自ら開発しています。

Vue系のドキュメントはもともとHexoという別の静的サイトジェネレータで作られていたんですが、それを載せかえようという背景があるみたいですね。

現在の最新バージョンは1.0alpha34。つまりv1代はアルファリリースという位置付けなのでまだまだ鋭意開発中という感じです。

所感

Gatsby.jsやNuxt.jsよりももっと純粋な静的サイトジェネレータ。開発中はVue.jsとホットローディングでサクサク開発していけて、最終的にbuildコマンドで出力されるのは完全に静的なサイト。Nuxt.jsのgenerateに似ている感じがしますね。

公式、非公式含めすでに幾つかの主要なプラグインやテーマは存在して、必要最小限の環境はありそうです。ただしブログ特化というわけではなく、むしろドキュメント特化方向なのでブログとしては自分でいろいろ手をかけてあげる必要がありそうです。

Vue.jsと同じように公式ドキュメントが良くできているので、Vue.jsやNuxt.jsをすでに触っている人ならかなり楽に入っていくことができますね。しかも自分で拡張するにあたって簡単にVue.jsのコンポーネントを増やして使うことができるのでサクサク行けます。

拡張はテーマ、プラグイン、エンハンサー(ミドルウェア的な位置付け?)と十分なレイヤーが整っています。テーマのカラースキームはオーバーライド可能な前提で作られてたり、いろいろと気が効いてる感じです。

1つのコンテンツ(ブログで言えば1記事)はMarkdownで書けます。Frontmatterも使えるので本文以外の付加情報を持たせることももちろん可能です。記事内で自作Vue.jsコンポーネントを使うこともできるので、ウィジェット的な用法もできそうです。例えば外部の参考リンクをブログカード的に表示するコンポーネントとか楽に作れそうですね。

めちゃくちゃ高機能を求めない、Vue.jsのコンポーネントを自分で作るスキルがある、という条件下であれば十分に実用可能な選択肢として入ってくるように思いました。

他と比較してみる

Hexo

Hexo

今のこのブログでも使われているHexoですが、Hexoは静的サイトジェネレータの中でもブログ特化しています。HugoのNode.js版とかそういう位置付けでしょうね。上にも書きましたがVuePressはまだそこは弱そうですね。

通常インデックスページに載るような投稿の一覧は自分で作る必要があります。とはいえ、全投稿を必要に応じてフィルタして、日付などで並び変えて、という感じで配列を作りv-forで回してやれば良さそうです。

Hexoはすでにある程度成熟していて便利なプラグインもいろいろ出そろっています。VuePressはVue.jsをすでに扱える人にとっては楽に拡張していける感じがすごいです。Vue.jsの単一ファイルコンポーネントによる、テンプレート、ロジック、スタイルがスコープに閉じているものとして開発できるのは楽ですね。

また将来性を考えてもEvanさん自ら開発しているという点も強いですね。Hexoはいつまで開発が継続されるかわかりませんし、まだこれからもJS系の技術はかなりのスピードで移り変わっていきそうです。Vue.jsの仕組みにのっかっているVuePressも完全に保証があるわけではないですが、コアな部分はVue.jsのレールに乗れる恩恵は大きそうですね。

個人的には、これから新しく作るならVuePressでやるかなあ。という感じですね。現に僕も今、冒頭で言ったようにHexoからVuePressに移行するためにシコシコ書いています。もちろんHexoの機能や、プラグインをそのまま実現しようとすると、機能がけっこう足りませんし、デフォルトのテーマではしっくり決ませんね。もともとそこまで高機能なブログにしてたわけではないですが、ちょっと頑張ればそこまで苦労なく移行できそうな気がします。

ちなみに現段階での移行はこんな感じです。

全くVuePress触ってない状態から1日程度でガワだけは移行できたのは

  • Vue.jsは普段から触っててそれなりにわかる
  • Hexoの時、Pugでコンポーネント的にテンプレートファイルを分割して作ってた
  • Hexoの時、Stylusで書いてた(VuePressのデフォルトCSSはStylus)

という条件が上手く噛み合わさったからかな、と思います。

とはいえ、まだ関連記事の実装とかメニューとか細ごまとしたものを実装しなければですし、今後の記事に有用そうな画像やブログカード的なコンポーネントも作っていかなきゃなあという感じですね。

Nuxt.js

はじめに - Nuxt.js

Nust.jsはVue.jsベースのアプリケーションフレームワークでSPA的なサイトを作るのが得意です。ですが、generateコマンドがあり、これはサーバーサイドを使わずに静的サイトとしてファイルを生成することができます。

VuePressはのNuxt.jsのgenerateにかなり近い感じはします。ただし、Nuxtはそもそもが静的サイトジェネレータではないので、そこは注意したほうが良いかも。逆に言えば静的サイトが欲しいだけでNuxt.jsのgenerateを使っててそれがオーバースペック気味ならば、VuePressを検討してみても良いかもしれません。

ただ、Nuxtのほうがノウハウが共有されているのでその面では分があるかもしれません。

Gatsby.js

GatsbyJS

Gatsby.jsはちょっとだけしか触ってないので厳密な比較は難しいことさきにお断りしておきます。Reactを日常的に使っていない身からしたらGatsbyは自分でカスタマイズしていくのに少々学習コストがかかるなぁという印象でした。とはいえ、Gatsby自体の仕組みがとてもよくできていて、少しの気概があればそれほど苦にならない気もしていますが。

なので日常的にReactを触る人にはGatsby.jsは非常にオススメだし、そしてなによりBlazing Fastです。ホントに。これはすごい。

カスタマイズは非常に柔軟な反面、テーマという仕組みがまだ成熟してなく、その変わりに各種StarterのようなScaffoldがいろいろ存在しているようです。これのツライところはテーマよりも深く違う部分が作れてしまうので、自分の思い通りになかなかカスタムできない。もしくはイチから作ろうとするとなかなか学習コストが高そう。という印象を受けました。

再度になりますが、このへんはReactに精通していれば大した障壁にはならなそうです。

まとめとこれからの方針

そんなわけで、まずHexoからVuePressに移行するのにトライしてみようと考えています。述べたようにVuePressはブログに適切なようにはまだ出来てないので、エクササイズがてらブログ的になるように自分でカスタムしていこうと思います。

それは自分のプロジェクト内で閉じた開発でも良いんですが、せっかくPluginやThemeという仕組みがあるので上手く切り出して自分で使うとしても外部モジュール扱いとして作っていこうと思います。

参加させていただいてるコミュニティにはGatsby.jsにコミットしてる猛者の方々がいたりしてGatsbyにはかなりそそられるんですが、得意なVueでひとまずはやってみようと思います。まだ発展途上なのでモジュールをどんどん作れる余地もあるでしょうし。

やっていこう!

新たなスタート、やっていくぞ!

  1. 1. 原因
  2. 2. Write Blog Every Week できなかった
  3. 3. 転職活動
  4. 4. 同時にどんどん負荷が上がる現職
  5. 5. 上がらない腰と機運
  6. 6. これからのアウトプット戦略

久々にブログを書きます。忙しかった最近が一旦は落ちついたのでやっていくぞ!という感じです。

原因

忙しかった理由としては

  1. 現職の業務負荷が高まって残業をたくさんせざるを得ないことになってた
  2. 転職活動をしていた

というのが重なったのが主な理由ですね。

あ、ちなみに転職は決まりました。6月からまた新たなスタートです。

Write Blog Every Week できなかった

昨年末ぐらいからWrite Blog Every Weekという素晴しいコミュニティの仲間に入れてもらっていました。要は1週間に1度はブログを書いていきましょうというコミュニティで、お互い声をかけあうことでよりブログを書く環境を作っていこう、というような趣旨です。参加メンバーのほとんどはエンジニアです。

3月ぐらいまではなんとかやっていたんですが、このブログの前回のポストが3月半ばであるように、多忙につき手がまわらず、他に優先すべきことが多くなってしまい継続できませんでした。

2週間できないまま3週目に突入すると参加規約に外れてしまって退会、という流れですが、即退会ということはなく一旦意思を確認されます。これはすごくありがたかったんですが、その時の現状を鑑みて、例え1週間程度延命したところで状況は変わらないと判断した結果、一旦脱会させていただきました。

転職活動

意思を9割固めたのは去年の年末ぐらい。1月半ばに査定面談という名のお沙汰言い渡しがあったんですが、お白洲の判決では自分の期待どおりの結果に全く届かなかったのが決定打でした。

それはかなり予想どおりではあったので、年明け早々からサイトに登録したり、エージェントと話したり、レジュメやアピールしやすいようなものを準備したりして、2月ぐらいから実際に面接に行ったりしていました。

一番多い時で5社平行して話を進めてたりしたんですが、そうなってくると現職の業務の隙を見ながら面接のスケジュール調整だけでも手一杯でした。もちろん面接に行く企業の情報もちゃんと調べたり、となりそれだけでプライベートの時間的余裕はなくなっていました。

この転職活動を通じていろいろ知見も広がったのでそれはまた別で言及したいと思います。

同時にどんどん負荷が上がる現職

現職で関与しているプロジェクトが佳境にさしせまりどんどん負荷があがりました。幸い月の労働時間ベースでのフレックスタイム制が導入された職場だったので、コアタイムの終わりが17時ということを利用して転職活動をしていました。

もちろんその分早く出社して前倒し戦略だったわけですが、負荷があがってきたため、転職活動のために17時ごろで退社or深夜残業ギリギリ一歩手前まで仕事、みたいな感じで働いていました。

結果で見れば転職活動は3月末に内定をいただいた事で決着しました。実はその時もまだ別の企業に面接や話を聞きに行く予定を入れていましたが、これも全てキャンセルしました。内定をいただいた企業への志望度が高く心を決めたという理由はもちろんですが、そんな時間が取れなくなってきたことが一番の理由でした。

そして転職活動中と変わらない前倒しな早い出社時間でありつつ、毎日深夜残業一歩手前で帰る日々がここ最近まで続いていました。つまり転職活動が終わった結果、働くことが可能な時間が増えてよけいに忙しくなった、という状況でした。

上がらない腰と機運

そんなこんなの4月が過ぎ、5月に入って退職を見据えた動きに入りました。ようやく以前のように人間的な時間に帰宅できるようになってきましたが、いざ忙しくなる前のようにやろうとするとなかなか腰が上がりませんでした。

もしかしたら以前のアウトプットに対する気概を支えていたのは転職活動に有利なように、という一面もあったからかもしれません。一度途切れてしまった情熱と習慣を取り戻すのはなかなか難しいですね。

ただ、以前のようにアウトプット駆動で知見を広げること、アウトプットすることによる知見の整理、アウトプットする習慣が自分にとってかなり有用な実感はあったのでなんとか再スタートしていきたいと思っていました。思ってはいましたがそれでも腰が上がりません。

そんな折にWrite Blog Every Weekのよしたくさんがメンバー募集をしているのを見て、再度の参加を決めました。
即決ではなく1日悩みました。実は近々転職以外のライフステージの大きな変化が訪れる予定があります。それによってやはりプライベート時間的余裕は減ることが容易に予想できます。なので、再度参加してもまたすぐに脱会するのではなかろうか、と。

ですが、これも機運だな、と思い参加をすることにしました。やはりまだ僕にはアウトプットを習慣にするにはまだ自分以外の力が必要ですし、時間のない中でもステップアップしていくという挑戦に手をつけるべきだと思ったからです。迷った時には一歩前へ進む、というのが僕の戦略でもあります。

これからのアウトプット戦略

実はブログを書いていない間、全くアウトプットしてなかったわけではないです。社内のナレッジベース的なものに知見を書くようにしていました。これはわりと気軽に体裁を整えずに得た知見を書いてたので楽にできていました。

また、Write Blog Every Weekの参加者のほとんどはエンジニアで、技術的な内容をメインとするブログをやってらっしゃる方ばかりです。ですが、明確にテクニカルな内容でなければならないという決まりもないようなのでこれからはもっと好きに書きます。

僕が休止してる間に、テクニカルな内容から外れてしまってきてるので、という理由で脱会されたかたもいらっしゃったようですが、僕は逆にもっとカジュアルに行こうと思います(もちろんスタンスの違いであって、その方への批判の意図はまったくありません)。

だからこういうちょっとエモい(?)話も書きますし、もっとライト内容で書いたりもします、ちゃんとしたテクニカルな話も書きますし、時にはまとまってないレベルでのテクニカルなことも書くでしょう。大事なのは今度こそ継続すること。前回失敗したのだからやり方を変えなかったらまた失敗するだけなので、その点を踏まえて良い意味でユルめて行くつもりです。

新たなスタートとして、頑張っていくぞ!

日々の回復力を最大化するささやかな抵抗

  1. 1. なぜなに回復力
  2. 2. 体と心の相関と体をないがしろにしないこと
  3. 3. 回復しやすくするために実践してること
    1. 3.1. 筋トレをする
    2. 3.2. ストレッチをする
    3. 3.3. 暴飲暴食をしない
    4. 3.4. 目薬をさして目を温める
    5. 3.5. お風呂でマッサージする、特に頭皮
    6. 3.6. 寝る前に養命酒を飲む
  4. 4. 最後に

始めに言っておきますが、最近テック系の話ばかり書いてきましたが、このブログはテック系寄りの雑記ブログなので今日はあまりテックと関係ないことを書きます。久々です。

最近の僕はというと、仕事のプロジェクトがなかなか佳境にさしかかって帰りが遅くなってきてます。それに加えてプライベートのほうもいろいろなことがおきていて、なかなか大変になってきました。
そんな毎日を乗りきるために「回復力」に焦点を当てたお話です。

忙しい毎日をのりきるための戦略として、

  • 最大体力を上げる
  • 回復力を上げる

という2つの戦略があると思いますがその後者のお話です。

ちなみに医学的な裏付けはとってませんので「僕はこうしてますよ」という1つの参考意見以上にはならないのでご注意ください。

なぜなに回復力

加齢により体力がなくなった、と昔武術に明けくれて今でも同年代の人よりも少しだけ体力に自信のある僕も感じます。でもつきつめて考えると最大HP的な体力がなくなったというよりは、減った最大HPが自然回復していくスピードが落ちたなと僕は実感しています。

体と心の相関と体をないがしろにしないこと

科学的に本当のところはどうかは知らないんですが、僕は体と心は相関関係があると思っています。腹が減ってるだけでちょっと怒りっぽくなることもあるし、今だったら花粉などのアレルギー症状が出てるだけでも少しイライラすることでしょう。冷えも禁物です、体温が下がると機嫌も体調も悪くなります。

いくら理性が発達しても、人間はしょせんヒトという生き物で、僕はまずそこを違わないように気をつけています。僕は心はどうコントロールすればできるのか良いかわかりません。でも体はわりとコントロールしやすいので、気持ちをハンドリングしたいときには体を介したアプローチをとるようにしています。これのわかりやすい例が深呼吸です。

ということで

  • なんだか疲れてる
  • 精神的に追いこまれてる
  • しんどい

という気持ちでいる日常であったとしても、とりあえず肉体を

  • 筋肉が疲労しすぎてたりや内臓が疲労していない状態
  • 肉体を抵抗なく動かせる状態
  • 身体の一時的な不便や痛みが気持ちに悪影響を及ぼしていない状態

に保つというアプローチはかなり有効だと思っています。

毎日を機嫌良く過ごすのはもちろん、判断力や決断力、心理的に障壁がある場合の踏み込む力にも影響しそうなので、体の心地良い状態に保つ戦略は良さそうに思います。特に長期的な視点で考えたときにその積み重ねは大きな差を生みそうです。

余談ですが僕にとっては一番良くないのは「疲れているから」という言い訳を用意できてしまうのが危ないです。僕はそこまで気持ちが強くないのでしばしば逃げ道を自分で塞ぐ戦略を取ることがあります。「疲れている」という使い勝手最強レベルの言い訳ができてしまうといろんなことが疎かになるので、疲れていない状態に保つのはけっこう重要だと感じています。

回復しやすくするために実践してること

筋トレをする

筋肉が増えることで体を楽に動かせたり、ということはもちろんあります。筋肉は全てを解決する、というなかば笑い話のような理論もありますが、僕はあながち間違いではなく7割ぐらいは解決してくれると思っています。

そんな話はさておき、コリの原因が筋肉が硬ばって血流が滞ってるということならば意識的に筋肉を動かしてみよう、というアプローチです。適度な運動はしたほうがいい、というのはもはや常識にもなっているはずです。

僕の場合は筋肉痛になるまでしてしまいますし、その筋肉痛を不快に思わず逆にトレーニングした実感として快と感じることもあるのですが、運動強度には気をつけてください。筋肉痛が酷いと普通は不快になって逆効果かもしれません。ですが、しっかり運動して筋肉を使って動かして、その結果寝付きが良くなったり眠りが深くなる効果も見逃がせません。特に頭を使う仕事の場合、いろいろ考えたりして睡眠の質が悪い場合など肉体疲労状態を意識的に作りだして強制的に眠りの質を上げる方法はあながち捨てたもんあじゃありません。

他の効果としてテストステロンの増加とか、トレーニング後の達成感がメンタルに効いたりしますが、そんな話はまた別の機会で。

ストレッチをする

筋トレに近いんですが、筋肉をのばしてあげる。これだけでも十分に効果があります。
簡単なヨガ的な大層だったり、ラジオ体操でも良いですね。習慣に無いと「めんどくさい」と思ってしまいますが時間にして5分とか10分ぐらいで十分。筋トレもそうですが、面倒なことってだいたい心理的障壁があるだけで実作業や実時間にすると大したことないんですよね。

ちなみにヨガをすると痩せるとか言いますが、だいたいヨガの動きはアイソメトリックな筋トレとストレッチの融合なので無理なく実践できる運動をした結果、健康指向になって普段の意識が変わり、結果的にに痩せた、というだけの話だと思っています。

暴飲暴食をしない

本当に加齢によって油っこいものがツラくなってくるんですね。まさか焼き肉行ってカルビを敬遠する日が自分に訪ずれると思いませんでした。

過ぎたお酒は眠りの質を悪化させますし、消化しきれない食べすぎもまた同様なようです。良く噛んで腹八分目、これを心掛けると内臓も疲労しにくく、眠りが消化のためだけに使われなく良い感じな気がします。睡眠という回復手段の効率を最大化させるための戦略ですね。

可能であれば早い時間に夕食を摂るのも有効なんですが、忙しいとどうしても帰宅が遅くなってしまって現実的に実行が難しかったりするのでそこはあきらめました。

目薬をさして目を温める

デスクワーク主体だと基本的に目が酷使されるので寝るときにやります。
眼精疲労に効く目薬をさして目元をあっためる系の何か(電熱系やらレンジや使い捨てなどいろいろ)を使う、これがなかなかに効きます。

寝るときにやると眠りやすくなる効果もあります。ちなみに僕はけっこう疲れてくると仕事のお昼休みにもやります。スッキリしていい感じです。目の疲れとともに目から耳のちょっと上辺りのコリが解れるような。

お風呂でマッサージする、特に頭皮

体のコリのあるところマッサージするぐらいはよくやると思うんですが、考えごとを沢山した日、目を酷使した日は頭皮もやります。頭皮は意外に忘れがちですが、脳や目の酷使は頭皮に来る気がしています。美容院に行ったときのマッサージを想像していただければわかりやすいかと。

ちなみに顔マッサージや耳をひっぱったりも良さそうですよ。とにかく張ってるところを緩める。マッサージやストレッチはコレです。人間、力の入れ方を学ぶことは多いんですが力の抜き方を学ぶ機会はそうそう無いんです。緩めるのは下手なんです。だから意図して緩めてあげる必要がありますね。

寝る前に養命酒を飲む

もうこの記事はこれが言いたくて書いたようなところがあります。それぐらい効きます(完全に体感でのお話です。体感は個人差があります)。
養命酒はイメージからどうも高齢者用に見られますし僕も以前はそう思ってました。ですがもしかしたら働き盛りの世代こそバッチリなのかもしれません。
もしかしたら栄養ドリンク系も同様の効果なのかもしれませんが、栄養ドリンクは多量のカフェインを含んでたりするので睡眠の質が落ちますし、場合によっては寝付けないこともあるかもしれません。

ただし少量のアルコールを含むため、体質的にアルコールが全く分解できないほうは要注意ですね。

最後に

見返してみると至極当たり前のことしか言ってないですね。ただしその当たり前をちゃんとやるか、意識的に実践するか、は長期戦を闘ううえで重要になると踏んでいます。

僕の実年齢はそう若くはないので若くて優秀な面々と張り合っていくにはこういう地道な方法もないがしろにできないな、と思っています。1年は1日×365。

肉体的にも精神的にも適度な負荷をかけつつ、日々を楽しく機嫌良く、うまいこと乗りきっていこうと思います。

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に決めたんだ、迷わないんだ!

UXデザインのペーパープロトタイプの講座を受けにいってきました

  1. 1. イベント概要
  2. 2. 参加動機
  3. 3. 講義について
  4. 4. 前半:ペーパープロトタイピングについて
  5. 5. 後半:ペーパープロトタイピング体験
  6. 6. まとめ

昨日、ユーリカ株式会社さんが催行しているUXトライアウト・2時間で学ぶペーパープロトタイプに、前回と同じくブログ書く枠として応募したところ抽選に当たったので参加させていただいたのでそのレポートです。

今回もまた詳細な内容は講義を受けたほうがより正しい内容なはずなので、主には概要と感想です。

イベント概要

UXデザイン会社のユーリカ株式会社さんで定期的に行なわれる「UXトライアウト」と題したコースがあって、基礎知識からUXデザインで行なわれる各行程ごとに2時間のワークショップ。どんなものがあるかはTECH PLAYのユーリカ株式会社|UXデザインで売れるモノづくりを応援しますのイベント・技術情報 - TECH PLAY[テックプレイ]で見ると良いと思います。

ちなみに通常は有料の講義だけどブログ書く枠は抽選枠ですが無料参加で募集しているようです。

参加動機

前回も書いたけど、良いものを作るためのUXデザインの話まで自分としても注力していきたいので。その技術を業務で活かしたいのはもちろんなんだけど、僕は個人開発も行なってるので個人でやる場合は全部を自分でやらなきゃいけない。

実は応募自体は前回のものと同時につづけざまにしたので前回と動機に違いはないものの、UXデザインの具体的な手法として例えばユーザーインタビューとかあるけど、まず個人レベルで手がつけやすそうなのはペーパープロトタイピングかなーと思っての応募。

講義について

前半は、ペーパープロトタイピングってどのようなものか、どうやってやるのか、やるための便利なツール(アプリなど)の紹介という感じ。
後半は実際にワークショップとしてやってみて、レビューしてみて、という感じの大きくは2部構成。

前半:ペーパープロトタイピングについて

まず話として、「へー」と思ったのが「評価」することを前提として作るということ。僕は何かをつくるときそれがWebでも趣味のレザークラフトでも基本的にまずラフスケッチをするんだけど、それは頭にあるものをちょっと具体化する程度のもので書き散らすことが多い。

工程としてそれも必要かもしれないけど、作るときは気分が乗ればガーっとコーディングから始めてしまう。だけどプロトタイプはちゃんと評価する、というのが違うんだなぁと思った。まあその結果が作ってる途中で「あ、これこうだとダメじゃん」とかって手戻りが発生するわけなんだけど。

で、アプリやWebの開発の場合、ペーパープロトタイピングでは全ての画面や動きなどをできるかぎり手書きでOKなので用意して、実際の操作を模して、紙を変えたり乗せたりしてシミュレーションする感じで評価するとのこと。

これは欠点として紹介されてたけど、その紙の差し替えは非常に面倒だという問題。なので例えば写真にとってデジタルツールと組み合わせてやると良いということでツールの紹介があった。

個人的にはここでFigmaの名前があがらなかったのが不思議だったけど、Sketchの名前も出なかったので、実際この講座は初心者向けなために、きっとそういうデザイナー寄りのツールではなくてまず使うための前提知識や技能が要求されないものをオススメしてくれたんだろうと思う。

ちょっと残念だったのは思ったよりこのツールの使いかたや類似ツールの説明などの話のボリュームを減らして、実際のペーパープロトタイピングの具体的なやり方とか気をつけること、守るべきルールとかをもうちょっと教えてほしかったなぁという気がしなくもない。アプリの使いかたはアプリが違えば変わってしまうので。もちろん僕は個人的にFigmaとかでやるだろうなぁ、と思ったという背景もあるけど。

後半:ペーパープロトタイピング体験

そんな説明があった後にじゃあ実際体験してみようということで出たお題は「Instagramを記憶、または想像だけで再現してみよう」というお題。

まず僕が作ったのはこんな感じ

実習

日頃から個人開発とかやるときに画面構成を書いたりしてるのと、業務でも仕様書的なものを見てるのでわりと想像はつきやすかった。

やって思ったのはInstagramはあまり使うほうではないけど、それでも迷うことなく使えている。かといってどこにどのような要素があったかというのは正確に把握してるわけでなく、なんとかそれなりに書けたのも、よくあるUIならここに置くだろうという推測で描いた要素のほうが多かった。

実際に簡易的な取り込みアプリをつかって、操作を模してレビューしてもらったりした。決定とか削除ボタンとか付けわすれた。あとは、何がどこにあるべきか、って思ったよりは難しかった。逆に面白かったのは、自分でちゃんと投稿されたかどうかのモーダルを想像できたり、「ここで多分GETのAPI叩いて、Arrayで返ってくるから」と想像してたのは無意識に発露したサーバーサイドエンジニア目線なんだろうなぁと感じて面白かった。

実際に体験してみるまでは、いきなりSketchとかFigmaとかのツールでやったほうが早くない? と思ったけどそうではなくって、触ることを意識したり、紙の状態でもいいからこれを触ったらこう反応して、というインタラクティブな動きをシミュレートすることで不足を炙りだすのにすごく役に立つと実感した。

なんというか「早く失敗する」というか。最初から最良のアウトプットを出そうとするんじゃなくて手書きでパパーっと描いてシミュレートする、過不足を早い段階で炙りだしてから問題なさそうなら次にもっとしっかりしたものをデジタルで作る、みたいな工程を組んだほうが結果的に早そう。

ただしそれが絶対というわけではなく、同じような画面や同じような部品のものは、手書きだと逆にコンポーネント化して使い回しとかできずにイチイチ書かなきゃいけないからかったるいなーとも思った。だからそういうところ上手く簡略化してやるのが良い落しどころなのかもなぁ。

まとめ

こうしてやってみたらやっぱり重要な工程だった。だけどこれだけやればじゃあUIに関してはバッチリかいうとそうではなく、使う人がどのような層なのかというコンテキストで最適な配置や表現する文言や使う単語、アイコンも変わってくる。だから他のユーザーインタビューやペルソナを想定したり、カスタマージャーニーマップも必要になってくるんだと思う。

2時間ではもちろん時間が足りないのでどうしても「お試し体験」的学習になるけど、それでも今回もなかなか良い学びでした。やっぱり何ごともトライして試して手を動かさないと。他の人はどうかわからないけど、とりあえず手をつける、体験してみる、が僕が僕を活かせる勉強のやりかただと思ってるし、そこを躊躇しないのが僕の強みだと思ってるので。

今回も今や便利ツールがいっぱいあるのに手書きでやることの意義とか効果とかも実感できたので良かった。今も職場で小さいホワイトボードが欠かせないように、デザインだけじゃなくて、シーケンスやら分岐やら、設計もわりと手で描いてみて考えるタイプなので、手書きを活かすアイデアが1つ足されたような感じ。

それと同時に余裕があれば(といってるうちはいつまでたってもそんな日は訪れないんだけど)Figmaとかももっと触っていきたいと思った。

今後もUX系の講座には参加したいと思うものの、今はそれが本業ではないのでいったんはこれくらいにして、紹介された本やUIに関するガイドラインとかをまず読むところからひとつづつ一歩づつ進むことにしよう。粛々と。

コンポーネント時代の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命名をどのようなルールでやってるか凄く興味があるなぁ。