いろいろな Web フロントエンドの技術

最近フロントエンドのあれこれに触ることが多い。フロントエンドといえば諸々のつらみとの戦いである。それらを解決するいろんな技術があるがいろいろありすぎてとっつきづらいので各技術が何を解決したいのかまとめてみた。

各技術の詳細は、それぞれ詳しい記事が世の中に溢れてるので省く。包括的にどんな技術があるのか、をみつめることをこの記事の価値におく。

(各技術は、それぞれ重複する部分もある;たとえばモジュールバンドラーである webpack はタスクランナー的な機能ももっている)


JavaScript フレームワーク

  • Vue.js / AngularJS / React

何を解決するか

  • JavaScript でビューを変更する / ビューの変更を受け取るときのつらみ
    • ビューとロジックの分離
    • ビューで変更があったらロジックが起動し、その結果を受けてビューが変更される、とかが書きやすくなる

JavaScript トランスパイル

何を解決するか

  • JavaScript の書きっぷりのつらみ
    • 動的型付け言語である JavaScript に型を付けられたりする
    • == を厳密に扱えたりする
    • 最新の ECMAScript の記法が使える
      • ブラウザによって JavaScript の挙動が異なる問題も併せて。

CSS トランスパイル

  • sass / less

何を解決するか

  • CSS のつらみ
    • 子孫セレクタを使わず、ネストすれば子孫関係が表現できる
    • 変数が使える
    • 演算ができる

テンプレートエンジン

  • Jade / Haml / Slim
  • そのほかサーバーサイドの言語によっていろいろ

何を解決するか

  • 静的な ​HTML を動的に生成したい問題
  • そのほか boilerplate
    • コピペで毎回書かなきゃいけないおまじない的なやつとか
    • < > はタイピングのコストが高いので避けようとしてるものが多い

パッケージ管理

  • npm / bower / yarn

何を解決するか

  • JavaScript のライブラリ管理
    • 欲しいライブラリをわざわざダウンロードしたり、クライアントに CDN にアクセスさせたりするコスト
    • ライブラリ間の依存性も解決できる

モジュールバンドル

  • webpack / browserify

何を解決するか

  • JavaScript / CSS を使用するためには ​HTML でどのファイルを使用するか指定する必要がある
    • 複数のファイルをまとめて hogehoge.bundle.js みたいなファイルにして、これさえ読み込めば OK、という体制にしてくれる
    • クライアントからのリクエストも減る
  • JavaScript / CSS のサイズが大きいとクライアント側にダウンロードの負荷がかかる
    • 改行とか無駄な記述をなくしてくれる (minify という)

スクランナー

  • gulp / Grunt

何を解決するか

  • 上述してきたツール群の多くは、コマンドを叩かなきゃ機能しない
    • たとえばファイルが保存されたら勝手にコマンドを起動する、とかの処理を記述できる

Node.js で Elasticsearch にクエリ

Elasticsearch の公式クライアントで叩く。

const elasticsearch = require('elasticsearch');
const client = new elasticsearch.Client({
  host: 'http://localhost:9200'
  httpAuth: 'user:password'
  log: 'trace'
});

client.search({
  index: index,
  type: type,
  size: size,
  body: {
    _source: [
      // to be included fields
    ],
    aggs: {
      // aggregation contents
    },
    query:{
      // query contents
    }  
  }
});

直で REST API 叩く時とちょっとだけ違っていて、 query や aggs の前に body を挟む必要があったりするので注意。

.search の戻り値は Promise になっていて、中身はこんな以下のように取得する。これは REST API の戻り値と (たぶん) おなじ。

client.search().then(response => {
  const documents = response.hits.hits.(x=>x._source);
  const aggregated = response.aggregations;
  // ...
});

当然、

function get() {
  return client.search().then(response => return response;)
}

get().then(result => {
  // ...
});

とか、

 const result = await client.search();

とか書ける。

Scroll API で取得したい場合はちょっと違って

let documents = [];
client.search({
  index: index,
  type: type,
  size: size,
  scroll: chacheTime
  body: {
    query:{
      // query contents
    }
  }
}, function scroll(error, response) {
  documents = documents.concat(response.hits.hits);
  if(response.hits.total !== documents.length) {
    client.scroll({
      scrollId: response._scroll_id,
      scroll: cacheTime
    }, scroll)
  } else {
    return documents;
  }
});

のようになる。この戻り値が Promise とならず、 client.search({...}, function(){...}).then() のように書けない。なので 上述の例と同じように扱いたければ

const promise = new Promise((resolve, reject) => {
  let documents = [];
  client.search({
    // ...
  }, function scroll(error, response) {
    documents = documents.concat(response.hits.hits);
    if(response.hits.total !== documents.length) {
      client.scroll({
        scrollId: response._scroll_id,
        scroll: cacheTime
      }, scroll)
    } else {
      resolve(documents);
    }
  })
})

と Promise オブジェクトを new してやる必要がある。

これはもっとスマートに解決できる方法があるかも?

Array.prototype.reduce() に親しむ

JavaScript の配列に reduce という関数がある。 MDM のドキュメント によると以下のように書かれている。

arrayObj.reduce(function(previousValue, currentValue, index, array){
  // ...
})

previous とか current いわれてもよくわからん。。 ただ、感覚を掴んでくるとこいつがなかなか便利だ。

たとえば、配列をループの中で処理する場合、そのままだと二重ループになっちゃって計算量がかさむってことがある。なので事前に配列じゃなくて連想配列だと嬉しい。そんなとき、こいつを使うと配列を連想配列に変換することができる。

array.reduce((map, obj) => {
  map(obj.key) = obj;
  return map;
}, {});

また Object.assign() とも相性がいい。これは、第一引数のオブジェクトに第二引数のオブジェクトをくっつけることができるというものだ。なので、たとえばオブジェクトの配列

const array = [
  {
    "1" : {
      ...
    },
  }
  {
    "2" : {
      ...
    }
  }
]

を使って

const result = array.reduce((map, obj) => {
  Object.assign(map, obj);
}, {});

すると、

result = {
  "hoge" : {
    ...
  },
  "fuga" : {
    ...
  }
}

のように変換できる。たとえば Node.js でデータを配列として取得して、 key-value として扱いたいってときに超便利。

Node.js × TypeScript 勉強中 (2)

Node.js × TypeScript 勉強中 - take a keen edge から続いた。


JavaScript の拡張メソッド (って呼ぶの?) 書くときはこうなる。

Array.prototype.chunk = function (size) {
    if (!this.length) return [];

    return [this.slice(0, size)].concat(this.slice(size).chunk(size));
}

で、 Node.js でこいつを別ファイルに切り出したい。そんなときは、

Array.prototype.chunk = function (size) {
  ...
}
exports = Array;

として、呼び出し側で

require('arrayExtensions');

function hoge(array) {
  const chunked = array.chunk(100);
}

してやる。モジュールじゃないので const extenstions = require('arrayExtensions') する必要はない。 TypeScript でいうと import extensions = require('arrayExtensions') みたいに書くと怒られる。

コピペばっかしてると import とか require とかがどの文脈のものなのかわからなくて詰みそうなところである。

ちなみに TypeScript の拡張メソッド (?) は ↑ だと怒られて、 interface を切る必要がある。

interface Array<T> {
    chunk(size: number): Array<T>;
}

Array.prototype.chunk = function (size) {
    ...
}
exports = Array;

打ち合わせで実現したいこと

良い打ち合わせは自然とは生まれないものです。自戒の意味も込めて考え直してみました。

参加者と、いわゆるファシリテーターの役割がごっちゃになっているので注意が必要かもしれません。


  • 目的を明確にする
    • 何のために集まっているか?
    • 情報を知ってもらう / 案を出す / 決断する(どれか1つの場合もあるし、複数の場合もある)
  • 進行する
    • 上記の目的が複数に該当する場合、いまどの段階なのか意識する
      • 参加者に意識してもらう、も大事
    • 意見が少ない(が、何か言いたそうな)人にタイミングよく振る
    • ある観点で情報が充足してきたら観点を増やす
    • 意見が盛り上がらなかったとき用の話題を出す
      • 事前に用意しとく
  • 次の行動を決める
    • 誰が / いつまで / 何を
      • 何を、はとくに曖昧になりがち;参加者全員で具体的に認識をあわせることが理想
    • もし早く終わったらさっさと終わる
  • 上記を準備しとく
    • できれば事前に展開しとく

これらは文字にしたら誰にとっても当然なことに見えると思いますが、なかなか(部分的にですら)達成された打ち合わせには出会えません。

あと、教科書的にはこれらを「常に意識しましょう」とか言いたくなりますが、何かを常に意識するなんて(少なくとも自分には)不可能なので、チェックポイント的に時間を区切って確認する、とかの技術も別途必要ですね。

ドヴォルザークの序曲「オセロ」について

ドヴォルザークの序曲「オセロ」作品93は、ここ数年で出会ったクラシック曲の中で断トツに好きな曲だ。

youtu.be

この曲は序曲3部作の3曲目らしい。2曲目はドヴォルザークの作品の中でも有名な「謝肉祭」。アマチュアオケの演奏会情報を掲載されている i-Amabile さん をみてみると、これまでの演奏回数は2017年12月19日現在で「謝肉祭」が119回、「オセロ」が11回と、10倍以上の差がある。たしかに、謝肉祭と比べられたら派手さは少ない。というか地味。

そんな地味なこの曲のどこが好きなのかというと、上の音源でいうと序盤である4:00以降に現れる、映画音楽的な勇ましい旋律、この旋律が一瞬で終わってその後同じ形では二度と登場しないところ。 こんなに格好いいのに。チャイコフスキーだったらこれをもう2、3回は使ってるよ。8:30あたりでもう一度現れると思いきや肩透かし。9:50あたりから、すこし形を変えて登場。でもあの頃の姿じゃない。コーダでついに来るか!と思ったらやっぱり来ない。で、ドヴォルザーク特有のアクロバティックなコード進行で突然終わり!

で聴き終わって、あの旋律がまた聴きたくなるんだ... 序盤にいいところを持ってきて、期待感を膨らませたまま、そのカタルシスを解放させるでもなく終わる。もしこれが映画だったら不快だろう。でも音楽なんだから、そのまま繰り返しても誰も文句を言わないはずだ。しかしドヴォルザークはそうせず、敢えて手間をかけて変奏した。1回聴いただけでは満足できない音楽体験、それを彼は目指していたんだ(本当か?)。

それでいて、何回も聴いているうちに、前奏も内声が充実しててクソ良いことに気づいたり、各楽器のソロ、アンサンブルがめっちゃ良かったり、そういえばドヴォルザークのここまで洗練されて劇的な曲って知らないかも?と思いはじめたり、「ここ完全に新世界じゃん!」とツッコミをいれたり(オセロと新世界は作曲時期が重複している;新世界のほうが完成が3年ほど後)、いろいろな楽しみ方ができるようになっている。

いつか演奏したい。

Elasticsearch でつまづいた話 (5)

Bucket Selector Aggregation というのがある。Terms Aggregation を柔軟にしたようなもので、「あるフィールドで特定の条件を満たすレコードの単位で集計」というような記述ができる。

下の例では 2 つのフィールドをまたがって条件を指定している。  

{
  "bucket_selector": {
    "buckets_path": {
      "my_var1": "the_sum", 
      "my_var2": "the_value_count"
    },
    "script": "params.my_var1 > params.my_var2"
  }
}

これを Nested Aggregation 等と組み合わせて使用したい場合、フィールド名の指定が素直にいかない。 Nested の外から Nested 以下のフィールドを指定しようとすると > を使ってたどる必要がある。

{
  "aggs" : {
    "nested_agg" : {
      "nested" : {
        "path" : "product"
      },
      "aggs" : {
        "sum_values" : {
          "sum" : {
            "field" : "price"
          }
        }
      }
    },
    "bucket_agg" : {
      "bucket_selector" : {
        "bucket_path" : {
          "var" : "nested_agg>price"
        },
        "script" : {
          "var > 100"
        }
      }
    }
  }
}

この > でフィールドをたどるのはおそらく bucket_selector に限った話ではないと思うが、調べてもなかなか情報に当たらなかったので結構なハマりポイントだと思う。