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全部募集 ● 大阪だけで一つのプロダクトの開発が完結するチームを作っていきます ● @グランフロント大阪(梅田駅直結ナレッジキャピタル) ● 東京本社と同じ水準の待遇