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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for KATO Kei KATO Kei
December 15, 2017
820

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

Avatar for KATO Kei

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 というのやってます