やっと sprockets やめる話 / goodbye sprockets

やっと sprockets やめる話 / goodbye sprockets

freee と sprockets の戦いの歴史は長い、その戦いについに終止符を打つときが来たという話です。

3b74c4caf7d59f84dff19d10dd1bcc56?s=128

Hiroki Nakashima

October 04, 2019
Tweet

Transcript

  1. freee 株式会社
 やっと sprockets やめる話
 2019.10.04 Gotanda.js #13

  2. • 経歴
 ◦ 2017〜 freee 会計チーム 人
 • 触ってるも 


    ◦ eb フロント Java cript, Flow
 ◦ uby on ails
 ◦ マイクロサービス golang
 ◦ CI/CD, docker, Kubernetes
 • 最近
 ◦ シンフォギア
 ▪ 最終回良かった
 ▪ 7年間 締めくくりとして最高だった
 ひも Twitter @him0net him0
 2
  3. freee と sprockets 戦い 歴史 長い
 そんな、戦いに終止符を打つ


  4. 打ちたいなー


  5. そんな話です


  6. freee 株式会社
 やっと sprockets やめる話
 2019.10.04 Gotanda.js #13

  7. 00 会計freee sprockets
 10 Section

  8. prockets ails - assets precompile
 sprockets
 
 // //= require

    jquery app/assets/javascript/summary.js 
 h1 { font-size: 100px; } app/assets/stylesheets/summary.css 
 /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=...(省略) app/assets/javascript/summary.xxx.js 
 h1 { font-size: 100px; } app/assets/javascript/summary.yyy.css 
 content hash 付き assets 
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx.js", "stylesheets/summary.css": "/assets/stylesheets/summary.yyy.css" } app/assets/manifest.json (実際 もう少し複雑な既述) 
 assets 参照 マップ
 CDN で配信する asset 生成
 gem 追加で ライブラリ 埋め込みや alt CSS や alt JS コンパイルを行うことも可能
  9. prockets ails - asset_path
 <head> <%= stylesheet_include_tag 'summary' %> </head>

    <body> <%= javascript_include_tag 'summary' %> </body> app/assets/views/summary/index.erb 
 <head> <link rel="stylesheet" href="/assets/stylesheets/summary.yyy.css"/> </head> <body> <script src="/assets/stylesheets/summary.xxx.js" /> </body> app/assets/javascript/summary/index.html 
 sprockets
 
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx.js", "stylesheets/summary.css": "/assets/stylesheets/summary.yyy.css" } app/assets/manifest.json (実際 もう少し複雑な既述) 
 assets 参照 マップ
 assets 参照出来る path を埋め込む
 assets path 埋め込み

  10. . 現状 prockets ってそもそも必要な ?
 フロントエンドビルド環境 充実により
 邪魔くさい存在になってきた
 sprockets
 


    gem 追加でライブラリ 埋め込みや alt CSS や alt JS コンパイルを行うことも可能 フロントエンド 更新に gem 更新 
 をまたなけれ ならない問題 
 webpack と 併用による 
 assets パイプライン 複雑化 問題 
 front/javascripts/* app/assets/javascripts/* public/assets/javascripts/* sprockets
 alt JS で開発 一旦 js で 吐き出す webpacker も同様な動作が できるが、独自 依存が多 く、入れるメリットが感じられ ない で入れていない
  11. 複雑でかつ遅いビルドフロー
 front/* app/assets/* public/assets/* sprockets
 ごめんwww 18分かかるわwww ごめんごwww Rails 全体を読み込んで初期化

    会計 freee コードベースがでかすぎて低速化 (他 原因もある かもしれないが深追いせず) precompile に不要なコードが実行速度を低下させていた
 3分
 15分
 CDN へ sync Rails Server
  12. freee エンジニア 、
 相手 目を見ただけで
 sprockets をやめたい気持ちを伝える
 特殊能力が発現していた


  13. 01 sprockets 戦歴
 16 Section

  14. sprockets を使わないフローにしたい
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset

    生成する世界が目指したい世界
 front/* public/assets/* CDN へ sync CDN へ sync 現在
 目指す
 未来

  15. そ ために 数々 課題があった


  16. L で説明する量で ない で(略)


  17. 歴代 雄姿 スライドで御覧ください
 • フロントエンド開発における革命とビルドプロセスについて - 2015/12
 ◦ glup, webpack

    導入
 • ails ailから解放される始め 一歩 - 2016/01
 ◦ webpck 導入
 • フロントエンド モダン化とJava criptモジュール 
依存解決 - 2016/06
 ◦ sprockets require 削除
 ◦ エントリーポイント 分割
 ◦ glup, webpack 導入
 • 大規模プロダクトにおけるフロントエンド 1年間 変化 - 2016/12
 ◦ webpack
 ◦ webpack ビルド 高速化
 • ailsアプリケーションにおけるフロントエンド環境 モダン化(人事労務フリー)

  18. 今回自分が立ち向かった 


  19. 歴代が残した最後 命題


  20. asset path 補完 問題
 Rails Railから解放される始め 一歩 


  21. prockets ails asset_path を使えない
 <head> <%= stylesheet_include_tag 'summary' %> </head>

    <body> <%= javascript_include_tag 'summary' %> </body> app/assets/views/summary/index.erb 
 <head> <link rel="stylesheet" href="/assets/stylesheets/summary.xxx.js"/> </head> <body> <script src="/assets/stylesheets/summary.yyy.css" /> </body> app/assets/javascript/summary/index.html 
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx.js", "stylesheets/summary.css": "/assets/stylesheets/summary.yyy.css" } app/assets/manifest.json (実際 もう少し複雑な既述) 
 sprockets を使わない
 当然 assets 参照 マップ
 生成出来ない
 assets 参照 path が分からない
 当然埋め込めない
 埋め込み 問題 生成 問題
  22. manifest.json 生成 問題を解決する
 const x = () => { console.log('test');

    } x(); app/assets/javascript/summary.js (E 6, J ) 
 section.content { h1 { font-size: 100px; } } app/assets/stylesheets/summary.sass 
 function x () { console.log('test'); } x(); app/assets/javascript/summary.xxx.js 
 section.content h1 { font-size: 100px; } app/assets/javascript/summary.yyy.css 
 content hash 付き assets 
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx.js", "stylesheets/summary.css": "/assets/stylesheets/summary.yyy.css" } app/assets/manifest.json 
 assets 参照 マップ
 (ただし sprockets と互換性なし)
 webpack で manifest.json を生成する
 webpack config 
 • webpack-manifest-plugin 導入
 • ファイル 出力に hash を入れる
 • file-loader を導入(css image)
 SASS 書き換えが必要
  23. asset path 補完 問題
 Rails Railから解放される始め 一歩 


  24. 現在 ビルド環境を壊すことなく
 webpack や ソースコード に手を入れる必要がある


  25. 並行運用できる構成(manifest 生成)
 const plugings = []; ...(省略) if (process.env.BUILD_ASSETS ===

    '1') { plugins.push( new ManifestPlugin({ fileName: 'javascripts-manifest.json', publicPath: 'assets/' }) ); } ...(省略) module.exports = { ...(省略) plugins, module: { rules: [ { test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }, { loader: 'replace-image-path-loader' }, // config 乗り換えが完了したら css 書き換えて消す { loader: 'replace-font-path-loader' }, // config 乗り換えが完了したら css 書き換えて消す { loader: 'sass-loader'} ] }, ] } } webpack.config.2.js
 { ...(省略) scripts: { build: "webpack --mode production", next:build: "webpack --mode production --config ./webpack.config.2.js", } } package.json
 もと config ビルドが 通らなくなる変更 loader で行い ソースコードを移行前 状態で保つ 必要な plugin 挿入 既存 ビルド環境を保ちつつ、新しい config 作り込みができる環境を作成
 もと config をコピペ 必要な plugin が入った config
  26. prockets ails asset_path を使えない
 <head> <%= stylesheet_include_tag 'summary' %> </head>

    <body> <%= javascript_include_tag 'summary' %> </body> app/assets/views/summary/index.erb 
 <head> <link rel="stylesheet" href="/assets/stylesheets/summary.xxx.js"/> </head> <body> <script src="/assets/stylesheets/summary.yyy.css" /> </body> app/assets/javascript/summary/index.html 
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx.js", "stylesheets/summary.css": "/assets/stylesheets/summary.yyy.css" } app/assets/manifest.json (webpack で生成) 
 assets 参照出来る path を埋め込む
 (参照するも が無い)
 生成 問題 埋め込み 問題 assets 参照 マップ
 (ただし sprockets と互換性なし)
 解決

  27. Action iew::Helpers::Asset rlHelper
 • asset_path (asset name を 受け取り path

    を返す)
 Action iew::Helpers::Asset agHelper
 • javascript_include_tag (script tag を生成)
 • stylesheet_link_tag (link tag を生成)
 • favicon_link_tag (link tag を生成)
 • image_tag (img tag を生成)
 asset_path を利用している箇所
 javascript_include_tag 'summary' <script src="/assets/stylesheets/summary.xxx.js" /> asset_path 'javascripts/summary.js' "/assets/stylesheets/summary.xxx.js" 内部的に asset_path 
 を呼び出している

  28. asset_path 置き換え
 ebpackAssetHelper
 • asset_path_from_manifest
 
 • javascript_bundle_tag
 • stylesheet_bundle_tag


    • favicon_bundle_tag
 • image_bundle_tag
 javascript_bundle_tag 'summary' <script src="/assets/stylesheets/summary.xxx.js" /> asset_path_from_manifest 'javascripts/summary.js' "/assets/stylesheets/summary.xxx.js" webpack manifest.json を読み取り path を補完する ヘルパーを作成、置き換えることにした 
 Action iew::Helpers::Asset rlHelper
 • asset_path
 Action iew::Helpers::Asset agHelper
 • javascript_include_tag
 • stylesheet_link_tag
 • favicon_link_tag
 • image_tag

  29. asset_path_from_manifest 振る舞い
 module WebpackAssetsHelper def self.manifest @manifest ||= compute_manifest end

    def self.compute_manifest manifest_file_path = File.join(Rails.root, 'public', 'assets', 'manifest.json') if File.exist?(manifest_file_path) JSON.parse(File.read(manifest_file_path)) else {} # manifest.json が見つからなかった場合、補完出来ないがそれを正しいも する end end def asset_path_from_manifest(asset_name) return asset_path(asset_name) unless use_webpack_manifest # URI 場合 return asset_name if asset_name =~ ::ActionView::Helpers::AssetUrlHelper::URI_REGEXP manifest = WebpackAssetsHelper.manifest if manifest.key?(asset_name) # manifest.json からハッシュ付きファイル名を取得する asset_path(manifest.fetch(asset_name)) else # 参照エラーを通知 ...(省略) asset_path(asset_name) end end private ...(省略) end webpack manifest に基づき asset path 補完 Rails Initialize で manifest を cache
  30. sprockets を使わないビルドフロー
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset

    生成する世界へ到達?
 front/* public/assets/* CDN へ sync CDN へ sync 現在
 目指す
 未来

  31. と言いたいところですが、いきなり こわい で


  32. asset_path_from_manifest 振る舞い
 module WebpackAssetsHelper def self.manifest @manifest ||= compute_manifest end

    def self.compute_manifest manifest_file_path = File.join(Rails.root, 'public', 'assets', 'manifest.json') if File.exist?(manifest_file_path) JSON.parse(File.read(manifest_file_path)) else {} # manifest.json が見つからなかった場合、補完出来ないがそれを正しいも する end end def asset_path_from_manifest(asset_name) return asset_path(asset_name) unless use_webpack_manifest # URI 場合 return asset_name if asset_name =~ ::ActionView::Helpers::AssetUrlHelper::URI_REGEXP manifest = WebpackAssetsHelper.manifest if manifest.key?(asset_name) # manifest.json からハッシュ付きファイル名を取得する asset_path(manifest.fetch(asset_name)) else # 参照エラーを通知 ...(省略) asset_path(asset_name) end end private ...(省略) end アプリケーションレベルで 切り戻し出来る仕組み webpack manifest で見つからなかった場合 sprocket manifest 補完に fallback
  33. 現在 目標 、一旦並行運用
 front/* app/assets/* public/assets/* sprockets
 front/* public/assets/* CDN

    へ sync CDN へ sync 3分
 15分
 3分
 { "javascripts/summary.js": "/assets/javascripts/summary/xxx .js", ... } app/assets/manifest.json (webpack で生成) 

  34. まとめ
 • 切り戻せる仕組み、安全に倒せる仕組みを常に考えておく
 ◦ 逆にそこだけ考えられれ 、どうにか進めていける
 • 並行運用して、安全を確認してから、次に進んでゆく
 ◦ 並行運用

    ために 、既存 ビルドを通しつつ
 ◦ 新しいビルド 設定も動く環境を用意して進めいくと良さげ

  35. 爆速でデプロイされて
 爆速で価値が提供できる世界へ
 下地 整った 


  36. よく考えたら、 ails 話がほとんどになってしまった 
 まあいっか


  37. ありがとうございました