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

やっと sprockets やめる話 / goodbye sprockets

やっと sprockets やめる話 / goodbye sprockets

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

Hiroki Nakashima

October 04, 2019
Tweet

More Decks by Hiroki Nakashima

Other Decks in Technology

Transcript

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


    ◦ eb フロント Java cript, Flow
 ◦ uby on ails
 ◦ マイクロサービス golang
 ◦ CI/CD, docker, Kubernetes
 • 最近
 ◦ シンフォギア
 ▪ 最終回良かった
 ▪ 7年間 締めくくりとして最高だった
 ひも Twitter @him0net him0
 2
  2. 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 コンパイルを行うことも可能
  3. 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 埋め込み

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


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

    会計 freee コードベースがでかすぎて低速化 (他 原因もある かもしれないが深追いせず) precompile に不要なコードが実行速度を低下させていた
 3分
 15分
 CDN へ sync Rails Server
  6. sprockets を使わないフローにしたい
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset

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

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

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

  8. 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 が分からない
 当然埋め込めない
 埋め込み 問題 生成 問題
  9. 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 書き換えが必要
  10. 並行運用できる構成(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
  11. 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 と互換性なし)
 解決

  12. 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 
 を呼び出している

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

  14. 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
  15. sprockets を使わないビルドフロー
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset

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

  16. 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
  17. 現在 目標 、一旦並行運用
 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 で生成)