成長中のプロダクトでフロントエンド環境改善を進める話
by
KATO Kei
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
成長中のプロダクトで フロントエンド環境改善を進める話 keik
Slide 2
Slide 2 text
自己紹介 名前: 加藤 慧 (KATO Kei) GitHub: keik Twitter: @keik_117 2017/1、通信系SIerからfreeeに転職
Slide 3
Slide 3 text
2015年末、freeeでは革命が起きていた
Slide 4
Slide 4 text
https://qiita.com/joe-re/items/1d5e1d0527cc439e03ef
Slide 5
Slide 5 text
https://qiita.com/joe-re/items/1d5e1d0527cc439e03ef
Slide 6
Slide 6 text
Sprockets ● Rails 標準装備のアセットプリプロセッサ ● CoffeeScript / Sass のビルド ● concat ● minify ● Gem ライブラリのロード
Slide 7
Slide 7 text
Sprockets ● Rails標準装備のアセットプリプロセッサ ● CoffeeScript / Sass のビルド ● concat ≠ 依存性の定義 ● minify ● Gem ライブラリのロード -> Gem 化されたものしか読み込めない ○ jquery-rails ○ backbone-rails ○ react-rails ○ freee-js (独自JSフレームワーク) ○ ...
Slide 8
Slide 8 text
//= require a //= require b console.log(‘a’) console.log(‘b’) application.js a.js b.js console.log(‘a’) console.log(‘b’) GET /assets/application.js Rails app require ディレクティブをつかった concat
Slide 9
Slide 9 text
人間による工夫 //= require lib //= require base //= require mvc // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} //= require mvc/view //= require mvc/model mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js //= require jquery lib.js
Slide 10
Slide 10 text
人間による努力(読み込むファイルの選択と順序) Uncaught TypeError: freee is not defined //= require lib //= require base //= require mvc base.js // ネームスペースを拡張する freee.mvc = {} //= require mvc/view //= require mvc/model mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js //= require jquery lib.js // ネームスペースをつくる freee = {}
Slide 11
Slide 11 text
人間による努力(読み込むファイルの選択と順序) Uncaught ReferenceError: $ is not defined //= require lib //= require base //= require mvc base.js // ネームスペースを拡張する freee.mvc = {} //= require mvc/view //= require mvc/model mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js //= require jquery lib.js // ネームスペースをつくる freee = {}
Slide 12
Slide 12 text
革命が目指す世界 Sprocekts ディレクティブ排除によって 単なる concat を越えた依存性の定義をあたえる
Slide 13
Slide 13 text
before 革命 //= require lib //= require base //= require mvc // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} //= require mvc/view //= require mvc/model mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js //= require jquery lib.js
Slide 14
Slide 14 text
after 革命(実績) require(‘./lib’) require(‘./base’) require(‘./mvc’) // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} require(‘./mvc/view’) require(‘./mvc/model’) mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js $ = require(‘jquery’) lib.js
Slide 15
Slide 15 text
after 革命(実績) require(‘./lib’) require(‘./base’) require(‘./mvc’) // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} require(‘./mvc/view’) require(‘./mvc/model’) mvc.js freee.mvc.View = function() { $(‘...’) } mvc/view.js freee.mvc.Model = function() { ... } mvc/model.js application.js //= require freee-js $ = require(‘jquery’) lib.js
Slide 16
Slide 16 text
No content
Slide 17
Slide 17 text
わかる ● Gem でロードしていたライブラリは Common JS で読み込めない ● グローバルに依存したコードはシンプルに除去できない 実際、このようなファイルが500個超あるゾ (views や models という他のネームスペースもあるゾ) class views.Employee extends freee.mvc.View ... @model = new models.Employee ... views/employee.coffee // Gem 版しかない(npm 版がない) //= require freee-js // npm 版がある const $ = require(‘jquery’)
Slide 18
Slide 18 text
革命まとめ めでたい ● Sprockets ディレクティブを部分的に Common JS に書き換え Babel (w/ Webpack on gulp) でトランスパイルするようになった ● Gem ではなく npm のライブラリを利用できるように ● ES.next が使えるように ● ESLint が使えるように ● flow による型チェックが導入できるように
Slide 19
Slide 19 text
革命まとめ 残る課題・新たな課題 ● Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ● 使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 ● Sprockets と Babel の多段変換により Source Map という人権を失う ● gulp によるビルドタスク複雑化と plugin 機構によるロックイン
Slide 20
Slide 20 text
革命まとめ 総括 ● 解決できた課題はあったものの、新たに生じた課題も多かった ● ツールは刷新されたが依存管理アーキテクチャのリファクタが伴わなかった ● アーキテクチャのリファクタには横断的かつ物量のある変更が必要 ● 成長を続けるコードベースの中でそれを実行する方法が見つけられなかった
Slide 21
Slide 21 text
革命の火を再びともそう
Slide 22
Slide 22 text
No content
Slide 23
Slide 23 text
※ freeeにきて5つ目に作ったPRだった
Slide 24
Slide 24 text
暴力革命から平和革命へ ● レビュー可能な変更 ● 正しそうな変更 ● 再現可能な変更 中長期的な改善が始まる
Slide 25
Slide 25 text
レビュー可能な変更 ● 物量が多いのは避けられない ● 小さなコンテキストで大きな横断的変更を作る 例 ● module.exports を付けて周るだけの変更 ● ネームスペースを削いで周るだけの変更 ● Common JS 化した module を import して周るだけの変更
Slide 26
Slide 26 text
No content
Slide 27
Slide 27 text
正しそうな変更 ● 静的解析を仕掛ける ● 機械的・法則的に変更を作る ● 人為ミスが入らないようにする 例 ● 現状のグローバル変数を ESLint の globals ルールや flow 型定義に定義し 一旦 Lint や型チェックを通すところから ● 一つずつ globals ルールを消してグローバル変数を削っていく ● 循環参照を検知する circular-dependency-plugin ● 正規表現置換で人為ミス防止 ● 正規表現置換の下処理として prettier でフォーマットしてマッチ漏れを防ぐ ● 理論的な正しさを求めるなら JSCodeshift とかでカッコよく AST いじる
Slide 28
Slide 28 text
# ネームスペースを削ぐ perl -i -pe 's/models\.(?=[A-Z])//g' **/*.{js,coffee} # class 定義に module.exports を付与する perl -i -pe 's/^class/module.exports = class/g' **/*.{js,coffee}
Slide 29
Slide 29 text
再現可能な変更 ● そうこうしている間にもプロダクトは成長を続ける ● conflictする前提で、もう一度すぐに変更を再現できるようにする 例 ● 正規表現置換 [0] % history | wc -l 9999 [0] % history | grep grep | wc -l 2816 [0] % history | grep git | grep -v grep | wc -l 2768 [0] % history | grep perl | wc -l 601 [0] % history | grep yarn | wc -l 220 git と grep と perl で給料の6割を稼いでいる様子
Slide 30
Slide 30 text
進捗 require(‘./lib’) require(‘./base’) require(‘./mvc’) // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} require(‘./mvc/view’) require(‘./mvc/model’) mvc.js const $ = require(‘jquery’) module.exports = function View() { $(‘...’) } mvc/view.js module.exports = function Model() { ... } mvc/model.js application.js //= require freee-js $ = require(‘jquery’) lib.js
Slide 31
Slide 31 text
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ● 使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 ● Sprockets と Babel の多段変換により Source Map という人権を失う ● gulp によるビルドタスク複雑化と plugin 機構によるロックイン
Slide 32
Slide 32 text
Gem の Common JS 化 枯れているライブラリであれば、ソースコードをプロダクトのリポジトリ内に 取り込んだ上で改変するという割り切りが可能 ● バグも枯れていれば有益な修正が入る見込みも薄い ● ライブラリの運用方針のコンセンサスをとることなく、 自プロダクトにとって都合のいい改造ができる yarn Workspaces ● プロダクト内にプライベートパッケージを実現できる機能(yarn v1以降) ● 機能分離点ごとに内部パッケージ化して依存関係やファイルレイアウト整理 ● Gem のコードを Workspaces 内に放り込んで Common JS 化するのに活用
Slide 33
Slide 33 text
進捗 require(‘./lib’) // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} require(‘./mvc/view’) require(‘./mvc/model’) mvc.js const $ = require(‘jquery’) module.exports = function View() { $(‘...’) } mvc/view.js module.exporots = function Model() { ... } mvc/model.js application.js //= require freee-js lib.js
Slide 34
Slide 34 text
進捗 require(‘./lib’) // ネームスペースをつくる freee = {} base.js // ネームスペースを拡張する freee.mvc = {} require(‘./mvc/view’) require(‘./mvc/model’) mvc.js const $ = require(‘jquery’) module.exports = function View() { $(‘...’) } mvc/view.js module.exporots = function Model() { ... } mvc/model.js application.js //= require freee-js lib.js
Slide 35
Slide 35 text
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う ● gulp によるビルドタスク複雑化と plugin 機構によるロックイン
Slide 36
Slide 36 text
ビルドタスク分解 それぞれの CLI で十分 ● ESLint を gulp ではなく CLI から行う ● Webpack を gulp ではなく CLI から ● Sass ビルドを gulp ではなく node-sass の CLI から それぞれ npm-run-script から実行 ● タスクが疎結合になってツールが差し替えやすく ● gulp プラグインによるバージョンロックインを解消 ● 個々タスクの統合は npm-run-all で
Slide 37
Slide 37 text
Webpacker ● アセット配信はフロントエンドというよりかはサーバの話 ● Webpacker を使う ○ フィンガープリント付きのファイル生成 ○ Rails server と協調動作する設定済みの webpack-dev-server ○ Sprockets ディレクティブのようなロックインリスクが少なかった ● フロントエンドとサーバの歩み寄りのライン ○ 過激フロントエンド芸で自傷しない
Slide 38
Slide 38 text
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う ✔ gulp によるビルドタスク複雑化と plugin 機構によるロックイン
Slide 39
Slide 39 text
2018年も革命は続く……
Slide 40
Slide 40 text
次なる課題 ● 継続的な防御 ● 参照アプリケーションアーキテクチャ定義
Slide 41
Slide 41 text
継続的な防御 ● 未使用モジュール検知 webpack-unused ● 未到達コード検知 ESLint no-unreachable ルール ● 静的解析をCI連携
Slide 42
Slide 42 text
参照アプリケーションアーキテクチャ定義 ● 解釈の自由度が高い Flux アーキテクチャ ● ベストプラクティスに寄り添う ○ Redux (パターンが確立している) ○ Flux Standard Action ● 窮屈感? ○ どうなんだろう ○ 10日に1日、それぞれのメンバーが自分の興味中心で 自由な技術的課題に打ち込む Hack day というのやってます
Slide 43
Slide 43 text
freee 大阪拠点、できました ● 立ち上げメンバー募集中 ● エンジニア、PM、デザイナ、SRE全部募集 ● 大阪だけで一つのプロダクトの開発が完結するチームを作っていきます ● @グランフロント大阪(梅田駅直結ナレッジキャピタル) ● 東京本社と同じ水準の待遇