Upgrade to Pro — share decks privately, control downloads, hide ads and more …

成長中のプロダクトでフロントエンド環境改善を進める話

KATO Kei
December 15, 2017
730

 成長中のプロダクトでフロントエンド環境改善を進める話

KATO Kei

December 15, 2017
Tweet

Transcript

  1. Sprockets • Rails標準装備のアセットプリプロセッサ • CoffeeScript / Sass のビルド • concat

    ≠ 依存性の定義 • minify • Gem ライブラリのロード -> Gem 化されたものしか読み込めない ◦ jquery-rails ◦ backbone-rails ◦ react-rails ◦ freee-js (独自JSフレームワーク) ◦ ...
  2. //= 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
  3. 人間による工夫 //= 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
  4. 人間による努力(読み込むファイルの選択と順序) 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 = {}
  5. 人間による努力(読み込むファイルの選択と順序) 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 = {}
  6. 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
  7. 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
  8. 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
  9. わかる • 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’)
  10. 革命まとめ めでたい • Sprockets ディレクティブを部分的に Common JS に書き換え Babel (w/

    Webpack on gulp) でトランスパイルするようになった • Gem ではなく npm のライブラリを利用できるように • ES.next が使えるように • ESLint が使えるように • flow による型チェックが導入できるように
  11. 革命まとめ 残る課題・新たな課題 • Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト •

    使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  12. 正しそうな変更 • 静的解析を仕掛ける • 機械的・法則的に変更を作る • 人為ミスが入らないようにする 例 • 現状のグローバル変数を

    ESLint の globals ルールや flow 型定義に定義し 一旦 Lint や型チェックを通すところから • 一つずつ globals ルールを消してグローバル変数を削っていく • 循環参照を検知する circular-dependency-plugin • 正規表現置換で人為ミス防止 • 正規表現置換の下処理として prettier でフォーマットしてマッチ漏れを防ぐ • 理論的な正しさを求めるなら JSCodeshift とかでカッコよく AST いじる
  13. # ネームスペースを削ぐ perl -i -pe 's/models\.(?=[A-Z])//g' **/*.{js,coffee} # class 定義に

    module.exports を付与する perl -i -pe 's/^class/module.exports = class/g' **/*.{js,coffee}
  14. 再現可能な変更 • そうこうしている間にもプロダクトは成長を続ける • 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割を稼いでいる様子
  15. 進捗 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
  16. 残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト • 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  17. Gem の Common JS 化 枯れているライブラリであれば、ソースコードをプロダクトのリポジトリ内に 取り込んだ上で改変するという割り切りが可能 • バグも枯れていれば有益な修正が入る見込みも薄い •

    ライブラリの運用方針のコンセンサスをとることなく、 自プロダクトにとって都合のいい改造ができる yarn Workspaces • プロダクト内にプライベートパッケージを実現できる機能(yarn v1以降) • 機能分離点ごとに内部パッケージ化して依存関係やファイルレイアウト整理 • Gem のコードを Workspaces 内に放り込んで Common JS 化するのに活用
  18. 進捗 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
  19. 進捗 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
  20. 残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  21. ビルドタスク分解 それぞれの CLI で十分 • ESLint を gulp ではなく CLI

    から行う • Webpack を gulp ではなく CLI から • Sass ビルドを gulp ではなく node-sass の CLI から それぞれ npm-run-script から実行 • タスクが疎結合になってツールが差し替えやすく • gulp プラグインによるバージョンロックインを解消 • 個々タスクの統合は npm-run-all で
  22. Webpacker • アセット配信はフロントエンドというよりかはサーバの話 • Webpacker を使う ◦ フィンガープリント付きのファイル生成 ◦ Rails

    server と協調動作する設定済みの webpack-dev-server ◦ Sprockets ディレクティブのようなロックインリスクが少なかった • フロントエンドとサーバの歩み寄りのライン ◦ 過激フロントエンド芸で自傷しない
  23. 残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う ✔ gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  24. 参照アプリケーションアーキテクチャ定義 • 解釈の自由度が高い Flux アーキテクチャ • ベストプラクティスに寄り添う ◦ Redux (パターンが確立している)

    ◦ Flux Standard Action • 窮屈感? ◦ どうなんだろう ◦ 10日に1日、それぞれのメンバーが自分の興味中心で 自由な技術的課題に打ち込む Hack day というのやってます