フロントエンド開発に Babel も Webpack も必要ない ※

f:id:umai_bow:20180116124333j:plain

できらぁ!
面白法人カヤックのエンジニアのごんです。

昨今のフロントエンドといえば、
Todo アプリを作るにも Webpack やら Babel やら必要だと脅され、
始める前にうんざりしてしまうと話題ですが、
実は、最新のブラウザに限って言えば、そんなことはなく、
ECMAScript の新しい構文や JavaScript Modules など、
多くの機能がネイティブの状態でも使えます。

もちろん、古いブラウザのサポートや、通信パフォーマンスのことを考えると、
ビルドツールを使ったほうが良いのは間違いないのですが、
JavaScript の新しい機能に触れてみたり自分用のツールを作るぶんには十分でしょう。

ソースコードはこちら!

そんなわけで、今回はビルドツールなしでウェブアプリを作ってみました
ECMAScript に関するクイズゲームです。
最新版の Google Chrome や iOS Safari で動作します。
プリコンパイラなどは、一切使用していません

以下、利用した新しい構文や機能を紹介します。

import, export (JavaScript Modules)

import Vue from './vue.esm.browser.js';
import shuffled from './shuffled.js';

グローバルを汚染することなく、別ファイルを読み込める機能です
かつては require.js などもありましたが、import の場合は、実行前に全てのモジュールが読み込まれます。
ライブラリによっては、JavaScript Modules 形式のビルドを提供しているものもあるので、そのまま使えます。
今回は Vue.js も利用しました!

Async / Await

async mounted() {
  const res = await fetch("data/quizzes.json");
  this.originalQuizzes = await res.json();
  this.changeView('title');
}

JS をコールバック地獄から救う救世主です。
関数宣言で async という修飾子をつけると、関数内部で await 演算子を使うことができます。
Promise オブジェクトに対して await 演算子をつけることで、
新たなコールバックを作ることなく、平易な形で非同期処理を扱うことができます。
今回は fetch(これも割と新しいメソッド)と組み合わせることで、非常に簡単に通信処理を書くことができました。

分割代入

const { correctChoices, id } = quiz;

分割代入は、オブジェクトから値を取り出すときに便利です。
今回は使いませんでしたが、Spread Operator なども多くのブラウザで使えます。

const foo = { ...bar };

アロー演算子

return shuffled(this.originalQuizzes).map(quiz => {
  return Object.assign({}, quiz, { choices: shuffled(quiz.choices) });
});

this の扱いが格段に楽になるアロー演算子ももちろん使えます。

テンプレートリテラル

resultMessage() {
  return `${this.quizzes.length}問中${this.score}問でした!`;
}

なぜ今までなかったのか不思議なテンプレートリテラルも使えます。

map, forEach, reduce, includes など……

if(correctChoices.includes(this.answers.get(id))) {
  return score + 1;
}

配列の各種便利メソッドも使えます。

注意点

良いことばかりではないので、注意点も書いておきます。

  • JavaScript Modules が読み込み時に解決されるため、通信回数が増える
  • npm モジュールはそのまま使えない
  • JSX や TypeScript のようなトランスコンパイルはできない

おわりに

いかがでしたでしょうか。
これならメモ帳でも開発できますね。
いざビルドしたくなったときも、percel などを使えば(たぶん)手軽にできます。

より本格的なビルドツールに移行したくなったとしても、
ECMAScript ベースで書かれていれば、移行はそれほど難しくないはずです。

環境構築を恐れず、どんどん新しい技術に挑戦しましょうლ(´ڡ`ლ)

採用募集中です!

面白法人カヤックでは、
React とか Vue とか最新の ECMAScript とか使って
ガンガン開発したいエンジニアを大募集中です。

興味のある方は下記リンクからどうぞ!

中途採用 | 面白法人カヤック

コマンドライン用スクリプトにもClean Architectureを適用する

Lobi事業部サービス基盤チームの長田です。

今回はコマンドラインから実行するスクリプトのはなしです。

オペレーション用のスクリプト

どのプロジェクトにも、Webアプリケーションが実行するわけではなく、かと言って定期実行するわけでもない、 とはいえ管理画面にするまでもない小さなオペレーションがひとつやふたつや100個くらいあると思います。 スクリプトとして書いてしまうのは、たまにしか実行しないからとか、 管理画面としてつくるのがめんどくさい からかとか、そういうものが理由としてあげられるのではないでしょうか。 実際、手っ取り早く要求を満たせるのでスクリプトをホイホイ書きがちです。

課題

管理されていないスクリプトの山

Lobiはそこそこ運用年数が長いこともあって、このようなオペレーション用のスクリプトが 300 以上存在します。 中には(というか大半が)何に使うのかわからない古のスクリプトもありますし、頻繁に使うスクリプトでもテストがなかったりします。

手軽に書けるので、手軽に忘れがちです。 300以上のスクリプトのうち、用途が重複しているものがいくつあることか・・・。

ほんとに動くの?

スクリプトを書いた当時はもちろん動くでしょう。 が、そのスクリプトはいまも動くのでしょうか?

個別にスクリプトファイルを用意していると、コードをそれぞれのファイルに書きがちです。 「大したことをやっていないからスクリプトそのものに書いてしまおう」という気持ちが出てきます。

スクリプト上のコードは非常にテストしづらく、テストが書かれないこともままあります。 テストがない=CIされないコードは、実行できるのかどうかが実行してみないとわからない状態になってしまいます。

Lobiでは「本番環境で動作する全てのコードはコードレビューを通過しなければならない」というルールがあります。

スクリプト類も全てコードレビュー対象なので、危険なコードが本番環境上で実行されることはまずありません。 ただしそれはその時点で問題ないかどうかのレビューであって、今後も問題がないかのレビューではありません。

解決策

Clean Architecture

Lobiでは最近DDDを、ひいてはClean Architectureを実践しようという動きがあって、 コマンドラインから実行するスクリプトにもこれを適用してみてはどうかと考えました。

  • Frameworks & Drivers -> コマンドライン
  • Interface Adapters -> ユースケースを呼び出すためのコード
  • Application Business Rules -> ユースケースを記述したコード
  • Enterprize Business Rules -> DDDにおけるサービスやモデル

スクリプトが実行するべき処理をユースケースとして記述すればテストもしやすいですし、 そこから利用するサービスやモデルをしっかり定義しておけば管理画面化する際に流用が効きます。

スクリプトが重複してしまう問題も、命名の際に他のユースケースを見ながら考えることになるので発生しづらくなります。

なにより、ちょっとめんどくさいので「それなら管理画面を作ったほうがいいな」という気になります。

具体例

具体的なコード例を示します。 全てPerlで書かれています。 説明を簡単にするため、一部コードは省略しています。

  • cli.pl
    • Interface Adaptersにあたる
    • コマンドラインから実行される唯一のスクリプト
    • すべての処理はこのスクリプトから呼び出される
  • Lobi::CLI::Role::Base
    • Interface Adapters共通の設定
  • Lobi::CLI::Foo::DoSomething
    • Interface Adapterにあたる
    • コマンドライン引数を適切な形式に変換してユースケースに渡すだけ
  • Lobi::Usecase::Foo
    • Application Business Rulesにあたる
    • サービスやモデルを呼び出し目的の処理を行う

cli.pl

#!/usr/bin/env perl
use strict;
use warnings;

use Mouse::Util qw{load_class};

my $name = shift @ARGV;
my $class = "Lobi::CLI::${name}";
load_class $class;

$class->new_with_options->run();

Lobi::CLI::Role::Base

package Lobi::CLI::Role::Base;
use strict;
use warnings;

use Mouse::Role;

with "MouseX::Getopt";

has deploy => (
    is      => "ro",
    isa     => "Bool",
    default => 0,
);

requires qw{run};

1;

Lobi::CLI::Foo::DoSomething

package Lobi::CLI::Foo::DoSomething;
use strict;
use warnings;

use Mouse;

use Lobi::Usecase::Foo;
use Lobi::Domain::Model::Time;

with "Lobi::CLI::Role::Base";

has id => (
    is  => "ro",
    isa => "Int",
);

has date => (
    is  => "ro",
    isa => "Str",
);

sub run {
    my ($self) = @_;

    # ユースケースが受け取る形式に変換
    my $date = Lobi::Domain::Model::Time::from_string($self->date);

    return Lobi::Usecase::Foo::do_something(
        id   => $self->id,
        date => $date,
    );
}

1;

Lobi::Usecase::Foo

package Lobi::Usecase::Manager::Premium;
use strict;
use warnings;
use 5.012;

use Data::Validator;

sub do_something {
    state $rule = Data::Validator->new(
        id   => { isa => "Int" },
        date => { isa => "Lobi::Domain::Model::Time" },
    );
    my $args = $rule->validate(@_);

    # 何らかの処理
    ...
}

使い方としてはこんな感じです。

$ perl -Ilib script/op/cli.pl Foo::DoSomething --id=1234 --date='2017-11-24'

ちなみに

むかし書いたこんな記事を発掘しました。

バッチを手動実行するのはどんなとき? バッチ処理を手動で実行するのは、十中八九イレギュラーな状況が発生したときです。 ルーチンワークや実行の条件が決まっているものは何らかの方法で自動化できるはずです。

まったくもってそのとおりですね。

バッチファイルに処理の本体を書かない

はい。

おわり

スクリプトだったものをユースケースとして実装することで、 「課題」で挙げたものが全て解決できるわけではありません。 命名規則がなければ同じようなユースケースが量産されてしまうでしょうし、 そもそもよく使うなら管理画面にしなさいよという話もあります。 それでも「テストを書くこと」「層構造のアーキテクチャを意識すること」を 心構えとしてではなく仕組みとして強制することで、一定の効果が見込めると考えています。

今回の話題は、実はLobiの関連サービスであるLobi Tournamentで実績のある方式を Lobi本体サービス用にアレンジにして取り込んだものです。 小回りの効く若いプロジェクトとある程度の規模で歴史のあるプロジェクトがお互いに学びあって、 良いところを取り込み悪いところを知って事前に避けることができるのは良い環境だと思います。

カヤックではより良いコードを書くために試行錯誤したいエンジニアも募集しています!