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

E2151539bcd4e5504e6da8c0b3546717?s=47 KATO Kei
December 15, 2017
520

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

E2151539bcd4e5504e6da8c0b3546717?s=128

KATO Kei

December 15, 2017
Tweet

Transcript

  1. 7.

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

    ≠ 依存性の定義 • minify • Gem ライブラリのロード -> Gem 化されたものしか読み込めない ◦ jquery-rails ◦ backbone-rails ◦ react-rails ◦ freee-js (独自JSフレームワーク) ◦ ...
  2. 8.

    //= 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. 9.

    人間による工夫 //= 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. 10.

    人間による努力(読み込むファイルの選択と順序) 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. 11.

    人間による努力(読み込むファイルの選択と順序) 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. 13.

    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. 14.

    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. 15.

    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. 16.
  10. 17.

    わかる • 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’)
  11. 18.

    革命まとめ めでたい • Sprockets ディレクティブを部分的に Common JS に書き換え Babel (w/

    Webpack on gulp) でトランスパイルするようになった • Gem ではなく npm のライブラリを利用できるように • ES.next が使えるように • ESLint が使えるように • flow による型チェックが導入できるように
  12. 19.

    革命まとめ 残る課題・新たな課題 • Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト •

    使い続けたい Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  13. 22.
  14. 26.
  15. 27.

    正しそうな変更 • 静的解析を仕掛ける • 機械的・法則的に変更を作る • 人為ミスが入らないようにする 例 • 現状のグローバル変数を

    ESLint の globals ルールや flow 型定義に定義し 一旦 Lint や型チェックを通すところから • 一つずつ globals ルールを消してグローバル変数を削っていく • 循環参照を検知する circular-dependency-plugin • 正規表現置換で人為ミス防止 • 正規表現置換の下処理として prettier でフォーマットしてマッチ漏れを防ぐ • 理論的な正しさを求めるなら JSCodeshift とかでカッコよく AST いじる
  16. 28.

    # ネームスペースを削ぐ perl -i -pe 's/models\.(?=[A-Z])//g' **/*.{js,coffee} # class 定義に

    module.exports を付与する perl -i -pe 's/^class/module.exports = class/g' **/*.{js,coffee}
  17. 29.

    再現可能な変更 • そうこうしている間にもプロダクトは成長を続ける • 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割を稼いでいる様子
  18. 30.

    進捗 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
  19. 31.

    残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト • 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 • Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  20. 32.

    Gem の Common JS 化 枯れているライブラリであれば、ソースコードをプロダクトのリポジトリ内に 取り込んだ上で改変するという割り切りが可能 • バグも枯れていれば有益な修正が入る見込みも薄い •

    ライブラリの運用方針のコンセンサスをとることなく、 自プロダクトにとって都合のいい改造ができる yarn Workspaces • プロダクト内にプライベートパッケージを実現できる機能(yarn v1以降) • 機能分離点ごとに内部パッケージ化して依存関係やファイルレイアウト整理 • Gem のコードを Workspaces 内に放り込んで Common JS 化するのに活用
  21. 33.

    進捗 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
  22. 34.

    進捗 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
  23. 35.

    残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う • gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  24. 36.

    ビルドタスク分解 それぞれの CLI で十分 • ESLint を gulp ではなく CLI

    から行う • Webpack を gulp ではなく CLI から • Sass ビルドを gulp ではなく node-sass の CLI から それぞれ npm-run-script から実行 • タスクが疎結合になってツールが差し替えやすく • gulp プラグインによるバージョンロックインを解消 • 個々タスクの統合は npm-run-all で
  25. 37.

    Webpacker • アセット配信はフロントエンドというよりかはサーバの話 • Webpacker を使う ◦ フィンガープリント付きのファイル生成 ◦ Rails

    server と協調動作する設定済みの webpack-dev-server ◦ Sprockets ディレクティブのようなロックインリスクが少なかった • フロントエンドとサーバの歩み寄りのライン ◦ 過激フロントエンド芸で自傷しない
  26. 38.

    残る課題 ✔ Sprockets ディレクティブの糖衣構文としての Common JS 導入に留まり、 依存管理アーキテクチャは依然としてグローバルコンテキスト ✔ 使い続けたい

    Gem を読み込むため Sprockets ディレクティブが残存 ✔ Sprockets と Babel の多段変換により Source Map という人権を失う ✔ gulp によるビルドタスク複雑化と plugin 機構によるロックイン
  27. 42.

    参照アプリケーションアーキテクチャ定義 • 解釈の自由度が高い Flux アーキテクチャ • ベストプラクティスに寄り添う ◦ Redux (パターンが確立している)

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