Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
成長中のプロダクトでフロントエンド環境改善を進める話
Search
KATO Kei
December 15, 2017
830
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
成長中のプロダクトでフロントエンド環境改善を進める話
KATO Kei
December 15, 2017
Featured
See All Featured
The Language of Interfaces
destraynor
162
27k
Writing Fast Ruby
sferik
630
63k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.6k
Building AI with AI
inesmontani
PRO
1
1.1k
A Modern Web Designer's Workflow
chriscoyier
698
190k
Automating Front-end Workflow
addyosmani
1370
210k
Odyssey Design
rkendrick25
PRO
2
720
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
250
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
500
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
230
First, design no harm
axbom
PRO
2
1.2k
Transcript
成長中のプロダクトで フロントエンド環境改善を進める話 keik
自己紹介 名前: 加藤 慧 (KATO Kei) GitHub: keik Twitter: @keik_117
2017/1、通信系SIerからfreeeに転職
2015年末、freeeでは革命が起きていた
https://qiita.com/joe-re/items/1d5e1d0527cc439e03ef
https://qiita.com/joe-re/items/1d5e1d0527cc439e03ef
Sprockets • Rails 標準装備のアセットプリプロセッサ • CoffeeScript / Sass のビルド •
concat • minify • Gem ライブラリのロード
Sprockets • Rails標準装備のアセットプリプロセッサ • CoffeeScript / Sass のビルド • concat
≠ 依存性の定義 • minify • Gem ライブラリのロード -> Gem 化されたものしか読み込めない ◦ jquery-rails ◦ backbone-rails ◦ react-rails ◦ freee-js (独自JSフレームワーク) ◦ ...
//= 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
人間による工夫 //= 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
人間による努力(読み込むファイルの選択と順序) 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 = {}
人間による努力(読み込むファイルの選択と順序) 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 = {}
革命が目指す世界 Sprocekts ディレクティブ排除によって 単なる concat を越えた依存性の定義をあたえる
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
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
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
None
わかる • 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’)
革命まとめ めでたい • Sprockets ディレクティブを部分的に Common JS に書き換え Babel (w/
Webpack on gulp) でトランスパイルするようになった • Gem ではなく npm のライブラリを利用できるように • ES.next が使えるように • ESLint が使えるように • flow による型チェックが導入できるように
革命まとめ 残る課題・新たな課題 • Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト •
使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
革命まとめ 総括 • 解決できた課題はあったものの、新たに生じた課題も多かった • ツールは刷新されたが依存管理アーキテクチャのリファクタが伴わなかった • アーキテクチャのリファクタには横断的かつ物量のある変更が必要 • 成長を続けるコードベースの中でそれを実行する方法が見つけられなかった
革命の火を再びともそう
None
※ freeeにきて5つ目に作ったPRだった
暴力革命から平和革命へ • レビュー可能な変更 • 正しそうな変更 • 再現可能な変更 中長期的な改善が始まる
レビュー可能な変更 • 物量が多いのは避けられない • 小さなコンテキストで大きな横断的変更を作る 例 • module.exports を付けて周るだけの変更 •
ネームスペースを削いで周るだけの変更 • Common JS 化した module を import して周るだけの変更
None
正しそうな変更 • 静的解析を仕掛ける • 機械的・法則的に変更を作る • 人為ミスが入らないようにする 例 • 現状のグローバル変数を
ESLint の globals ルールや flow 型定義に定義し 一旦 Lint や型チェックを通すところから • 一つずつ globals ルールを消してグローバル変数を削っていく • 循環参照を検知する circular-dependency-plugin • 正規表現置換で人為ミス防止 • 正規表現置換の下処理として prettier でフォーマットしてマッチ漏れを防ぐ • 理論的な正しさを求めるなら JSCodeshift とかでカッコよく AST いじる
# ネームスペースを削ぐ perl -i -pe 's/models\.(?=[A-Z])//g' **/*.{js,coffee} # class 定義に
module.exports を付与する perl -i -pe 's/^class/module.exports = class/g' **/*.{js,coffee}
再現可能な変更 • そうこうしている間にもプロダクトは成長を続ける • 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割を稼いでいる様子
進捗 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
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト • 使い続けたい
Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
Gem の Common JS 化 枯れているライブラリであれば、ソースコードをプロダクトのリポジトリ内に 取り込んだ上で改変するという割り切りが可能 • バグも枯れていれば有益な修正が入る見込みも薄い •
ライブラリの運用方針のコンセンサスをとることなく、 自プロダクトにとって都合のいい改造ができる yarn Workspaces • プロダクト内にプライベートパッケージを実現できる機能(yarn v1以降) • 機能分離点ごとに内部パッケージ化して依存関係やファイルレイアウト整理 • Gem のコードを Workspaces 内に放り込んで Common JS 化するのに活用
進捗 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
進捗 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
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい
Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
ビルドタスク分解 それぞれの CLI で十分 • ESLint を gulp ではなく CLI
から行う • Webpack を gulp ではなく CLI から • Sass ビルドを gulp ではなく node-sass の CLI から それぞれ npm-run-script から実行 • タスクが疎結合になってツールが差し替えやすく • gulp プラグインによるバージョンロックインを解消 • 個々タスクの統合は npm-run-all で
Webpacker • アセット配信はフロントエンドというよりかはサーバの話 • Webpacker を使う ◦ フィンガープリント付きのファイル生成 ◦ Rails
server と協調動作する設定済みの webpack-dev-server ◦ Sprockets ディレクティブのようなロックインリスクが少なかった • フロントエンドとサーバの歩み寄りのライン ◦ 過激フロントエンド芸で自傷しない
残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい
Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う ✔ gulp によるビルドタスク複雑化と plugin 機構によるロックイン
2018年も革命は続く……
次なる課題 • 継続的な防御 • 参照アプリケーションアーキテクチャ定義
継続的な防御 • 未使用モジュール検知 webpack-unused • 未到達コード検知 ESLint no-unreachable ルール •
静的解析をCI連携
参照アプリケーションアーキテクチャ定義 • 解釈の自由度が高い Flux アーキテクチャ • ベストプラクティスに寄り添う ◦ Redux (パターンが確立している)
◦ Flux Standard Action • 窮屈感? ◦ どうなんだろう ◦ 10日に1日、それぞれのメンバーが自分の興味中心で 自由な技術的課題に打ち込む Hack day というのやってます
freee 大阪拠点、できました • 立ち上げメンバー募集中 • エンジニア、PM、デザイナ、SRE全部募集 • 大阪だけで一つのプロダクトの開発が完結するチームを作っていきます • @グランフロント大阪(梅田駅直結ナレッジキャピタル)
• 東京本社と同じ水準の待遇