こんにちはフロントエンドエンジニアです
こんにちはフロントエンドエンジニアの高野です。
最近は約束のネバーランドという漫画にハマっています。
ハウス食品枠がSFとコラボしたみたいな内容で続きが早く読みたいです
ちなみに自分はハウス食品枠だとナンとジョー先生が好きでした。
フロントエンドエンジニア ≒ HTMLコーダー ≒ UXエンジニア
フロントエンドエンジニアって何をやる職業なのでしょうか?
mybestではどんなスキルが求められるのでしょうか?
mybestのフロントエンドについて
弊社はmybestというサービスを運営しており、フロントエンドエンジニアはそのWebサイトの運用に携わります。
入社当時の構成
主な構成は以下の通りです。バックエンドの構成については省略します
- Ruby on Rails の asset pipelineに則った構成
- assets/images
- assets/javascripts CoffeeScript 管理画面はTurbolinks
- asstes/stylesheets Sass with scss-lint
ざっくりいうと弊社のフロントエンドはRuby on Railsのガイドラインに則った控えめなJavaScriptの形に近かったと思います。
入社後感じた課題感
https://my-best.com/ を見る感じ、JavaScriptをゴリゴリ書いて実装している印象はあまり受けません。幸いにStylesheetに関しては前任の担当者がFLOCSSを取り入れたCSSに作り直してくれていたので特に課題を感じませんでした。FLOCSSに関しては下記の記事が参考になります。
BEM を使うべき5つの理由(なぜ BEM が G.R.E.A.T といえるのか)
https://github.com/hiloki/flocss
最も難しい問題は記事の管理画面の方でした。具体的にいくつか課題をあげます。
- フォーム送信時にJavaScriptでform部品を作成している
- jQueryのfind(), parent(). clone()などを駆使していてDOMとの依存度が高い
- パーツを入れ替えただけでもすべてのパーツを再描画するので時間がかかる
- 微妙にTurbolinlksの仕様と要件が噛み合っていないところがある
フォーム送信時にJavaScriptでform部品を作成している
$('body').on 'click', '.huga-submit-button', ->
values_element = $('<div></div>').hide().addClass('huga-values')
$(@).before(values_element)
huga_id = $("#huga-list").data("id")
values_element.append($('<input>').attr('name','id').attr('type','hidden').attr('value', huga_id))
huga_category_id = $("#huga-category-id").val()
huga_list_title = $("#huga-list-title").val()
huga_list_introduction = $("#huga-list-introduction").val()
huga_list_conclusion = $("#huga-list-conclusion").val()
values_element.append($('<input>').attr('name','huga_list[huga_id]').attr('type','hidden').attr('value', huga_list_category_id))
values_element.append($('<input>').attr('name','huga_list[title]').attr('type','hidden').attr('value', huga_list_title))
values_element.append($('<input>').attr('name','huga_list[introduction]').attr('type','hidden').attr('value', huga_list_introduction))
values_element.append($('<input>').attr('name','huga_list[conclusion]').attr('type','hidden').attr('value', huga_list_conclusion))
$('#huga-sortable .huga-parts').each ->
values_element.append($('<input>').attr('name', 'huga_list[order][]').attr('type','hidden').attr('value', $(@).data("cd")))
true
また、後述するTurbolinksの問題との兼ね合いもあり、このままだとフォーム送信時に送信されない値が出てきてしまうという問題がありました。
jQueryのfind(), parent(). clone()などを駆使していてDOMとの依存度が高い
$(this).parent().siblings(".foo-data").remove();
とか
someElement = $(’#foo-trigger').parent('bar').find('baz').clone()
$('#hoge').parent('huga).append(someElement)
みたいなやつです。
これがなかなか、一つのクロージャのなかで一回宣言されてDOMに追加されるとかならまだ目で追えるのですが、
$('body').on 'click', '.some-parts', ->
someElement1 = $(’#foo1').parent('bar1').find('baz1').clone()
someElement2 = $(’#foo2').parent('bar2').find('baz2').clone()
someElement3 = $(’#foo3').parent('bar3').find('baz3').clone()
someElement4 = $(’#foo4').parent('bar4').find('baz4').clone()
$('#hoge1').parent('huga1').append(someElement1)
$('#hoge2').parent('huga2').append(someElement2)
$('#hoge3').parent('huga3').append(someElement3)
$('#hoge4').parent('huga4').append(someElement4)
$('#js-sortable .js-parts').each ->
values_element.append($('<input>').attr('name', 'item_list[order][]').attr('type','hidden').attr('value', $(@).data("cd")))
true
こんな感じで出てくるのですが、この実装の難点はDOMとの結合度が高く、リデザインなどでDOMの変更が生じた場合formに送信する値が変わってしまいやすいと思います。
パーツを入れ替えただけでもすべてのパーツを再描画するので時間がかかる
弊社の記事は非常に作り込まれており、一記事に対する画像や文字量はかなりボリュームがあります。
こちらはバグというよりも、Turbolinks経由で再描画する対象のパーツが多くなるにつれて、描画に時間がかかるという問題です。長ければ数分ぐるぐる回っていることもしばしばです。こちらは、仮想DOMによる差分描画を検討していますが未だ実装に入れてはいません。
微妙にTurbolinksの仕様と要件が噛み合っていないところがある
。TurbolinksはRails4からデフォルトで使える機能で、
- ドキュメント内のaタグを監視
- XHRでRailsのコントローラーを叩く
- コントローラーのメソッドがJavaScript(CoffeeScript)を返す
- 返却されたJavaScript(画面の再描画に必要な情報を返す)
みたいな流れで画面遷移することなく、画面を再描画するというものです。
この流れで何が問題なのかというと、TurbolinksはURLの変更を検知した場合、ブラウザ操作を行うとフォームの再送信のポップアップが出てきて、おkを押すとformが送信される仕様なのですが、弊社管理画面では一部遷移に伴いURLを変更させていたので、Turbolinksの仕様に気づかず、なぜか記事データが壊れてしまうというバグが頻出していました。(現在対応済み)
なんかいろいろ書きましたが、負債というよりは仕様が複雑すぎてコードから悲鳴が聞こえるみたいな感じです。次にどういうふうに変えていきたいか書きます。
あやふやな計画*(一部遂行済み)
これからマイベストのフロントまわりをどうしていきたいのか
一人だとなかなか先にすすまないので
是非ピンと来られた方お話させてください。
弊社のCTOも 酢がかかったコメを軽く握ったやつの上に卵とか刺し身がのってるかもしれないものを食べにいくのもやぶさかではないと言っています。
Framework / UI library (着手)
こちらはすでにvue.jsを導入済みです。なぜvueなのかというと、公式にもあるように親しみやすさを感じたからです。アンチノミーみたいなのがあって落とし所がvueだったみたいなのもあります。
- フロントエンド自分しかいないので出来る限りモダンに振って(TypeScript + React + JSX )で行きたい
- フロントエンド自分しかいないので出来る限り他のロールのメンバーにもわかりやすいもので行きたい(jQuery + CoffeeScript_)
1.2の間をとってvueみたいな感じです。
このカルーセルはいままでOSSのライブラリを使っていましたが、UIの変更や、仕様変更に耐えられなくなったり、パフォーマンス・チューニングの目的でスクラッチで作り直しました。vue製です。
ちなみにvueには主に描画周りの責務をもたせてそれ以外の仕事ができるだけ外にだすように心がけています。
- convert処理が必要ならconvert.jsなどに記述
- HTTPRequestはPromiseの結果を受け取って描画に徹する
function convertParamsToFormValue(params) {
const specifications = PressUtils.createBlankSpecifications(params.specifications);
return {
parts: params.parts,
press: params.press,
item_part: {
specifications,
product: params.product,
},
};
}
出来るだけシンプルにしておくことで、後日描画系のライブラリを変えることになっても対応しやすくなればいいなとおもっています。
State Management / State Machine (未着手)
なにごとも無ければvuexを使おうと思っています。
具体的に考えられるのは管理画面のパーツの順番を変更した際の状態管理です
Static Type Checkers (着手)
テンプレート内でtypescriptの型チェックが効かないのが辛いですがtypescriptを使っています。
ガチで取り入れるというよりは段階的に使っていれ、フォームに送信するパラーメータの型を定義したり、関数の返り値を型指定したりなどゆるく、少しづつ積極的に取り入れていっています。
export default interface Params {
item_part: ItemPart, //ItemPart型を別途定義
id: String,
press: Press, //Press型を別途定義
parts: Parts, //Parts型を別途定義
order: String,
}
createBlankSpecifications: (specifications: [Types.Specification]) => {
// なにか処理
return specifications;
},
StyleBook / Design System (未着手)
デザインも日々着々とアップデートを重ねているのですが、課題感としてデザイナーと共有して議論するツールが複数あり、(デザインカンプとか、本番コードとか、コンフルエンスとかですね)UIコンポーネントをまとめる場所がほしいなぁと思っています。一緒に新しいあたりまえを作りましょう
こちらはいつも僕が指を咥えて見ているshopifyのPolarisです。もう名前がいいですよね。
余談ですがvueはSingle File Componentの構成をとっており、scoped cssが使えるのでそこらへんも考えたいですね!
Build tools (未着手)
rails/webpackerを採用していますが、webpackerのwebpackのバージョンが上がらないことで軽微な問題も発生してきており、webpackにしようと思っています
Testing (未着手)
こちらは速い段階で mochaによる単体テストを入れます。ユニットテストに関しては考え中です。
その他
- Rx.jsはいつでも入れる用意があります。
- Nuxt.jsとかのServer Side Renderingに関しては今の所導入予定はありませんが、サーバーサイドエンジニアとGraphQLの導入を検討しているのでApollo Clientとかもしかしたら使うかもしれません。他のWeb APIスキーマ御三家を検討されたい方は今がチャンスかもしれません。
- Service workerなどWeb APIとはどんどん仲良くしたいと思っています。
まとめ
だらだらと想いを書きましたがざっくりどうしていくのかというとこんな感じです。
- jQueryで複雑な処理をしているところの置き換えとしてvue.js + TypeScript でいく
- Turbolinks絡みの実装が複雑なので出来る限りTurbolinksには責務以外の仕事をさせないもしくはTurbolinksやめる
- 状態管理が煩雑になりそうなところ(パーツの部品管理)はState Management( vuex )を採用する
- テスト書く(はよ書け to 自分)
- Rx.jsはいつでも入れる用意があります。
- デザインガイドを作成したい
現場からは以上です。
最後に
mybestは今年アドベントカレンダーをします。エンジニア以外にもライターや編集者、マーケターなどが興味ある分野で自由に書きます。乞うご期待ください。
https://adventar.org/calendars/3397
おわりに
mybestがMonoMaxさんと協力したムック本が出ました。