成長中のプロダクトでフロントエンド環境改善を進める話
by
KATO Kei
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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全部募集 ● 大阪だけで一つのプロダクトの開発が完結するチームを作っていきます ● @グランフロント大阪(梅田駅直結ナレッジキャピタル) ● 東京本社と同じ水準の待遇