Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
やっと sprockets やめる話 / goodbye sprockets
Search
Hiroki Nakashima
October 04, 2019
Technology
1
6.4k
やっと sprockets やめる話 / goodbye sprockets
freee と sprockets の戦いの歴史は長い、その戦いについに終止符を打つときが来たという話です。
Hiroki Nakashima
October 04, 2019
Tweet
Share
More Decks by Hiroki Nakashima
See All by Hiroki Nakashima
freee会計からマイクロサービスを切り出すのに4年かかりました / 4 Years for Carving Out A Micro Service from freee Accounting.
him0
11
29k
開発速度を支える技術 / The Techniques for Keeping The Development Speed
him0
0
3.2k
Other Decks in Technology
See All in Technology
生成AIが変えるデータ分析の全体像
ishikawa_satoru
0
170
OTelCol_TailSampling_and_SpanMetrics
gumamon
1
190
サイバーセキュリティと認知バイアス:対策の隙を埋める心理学的アプローチ
shumei_ito
0
390
複雑なState管理からの脱却
sansantech
PRO
1
150
OCI Vault 概要
oracle4engineer
PRO
0
9.7k
これまでの計測・開発・デプロイ方法全部見せます! / Findy ISUCON 2024-11-14
tohutohu
3
370
EventHub Startup CTO of the year 2024 ピッチ資料
eventhub
0
120
OCI Security サービス 概要
oracle4engineer
PRO
0
6.5k
アジャイルチームがらしさを発揮するための目標づくり / Making the goal and enabling the team
kakehashi
3
110
リンクアンドモチベーション ソフトウェアエンジニア向け紹介資料 / Introduction to Link and Motivation for Software Engineers
lmi
4
300k
OCI Network Firewall 概要
oracle4engineer
PRO
0
4.2k
Platform Engineering for Software Developers and Architects
syntasso
1
520
Featured
See All Featured
Visualization
eitanlees
145
15k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
410
Faster Mobile Websites
deanohume
305
30k
Designing for Performance
lara
604
68k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
Site-Speed That Sticks
csswizardry
0
27
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
BBQ
matthewcrist
85
9.3k
It's Worth the Effort
3n
183
27k
A Modern Web Designer's Workflow
chriscoyier
693
190k
Transcript
freee 株式会社 やっと sprockets やめる話 2019.10.04 Gotanda.js #13
• 経歴 ◦ 2017〜 freee 会計チーム 人 • 触ってるも
◦ eb フロント Java cript, Flow ◦ uby on ails ◦ マイクロサービス golang ◦ CI/CD, docker, Kubernetes • 最近 ◦ シンフォギア ▪ 最終回良かった ▪ 7年間 締めくくりとして最高だった ひも Twitter @him0net him0 2
freee と sprockets 戦い 歴史 長い そんな、戦いに終止符を打つ
打ちたいなー
そんな話です
freee 株式会社 やっと sprockets やめる話 2019.10.04 Gotanda.js #13
00 会計freee sprockets 10 Section
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 コンパイルを行うことも可能
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 埋め込み
. 現状 prockets ってそもそも必要な ? フロントエンドビルド環境 充実により 邪魔くさい存在になってきた sprockets
gem 追加でライブラリ 埋め込みや alt CSS や alt JS コンパイルを行うことも可能 フロントエンド 更新に gem 更新 をまたなけれ ならない問題 webpack と 併用による assets パイプライン 複雑化 問題 front/javascripts/* app/assets/javascripts/* public/assets/javascripts/* sprockets alt JS で開発 一旦 js で 吐き出す webpacker も同様な動作が できるが、独自 依存が多 く、入れるメリットが感じられ ない で入れていない
複雑でかつ遅いビルドフロー front/* app/assets/* public/assets/* sprockets ごめんwww 18分かかるわwww ごめんごwww Rails 全体を読み込んで初期化
会計 freee コードベースがでかすぎて低速化 (他 原因もある かもしれないが深追いせず) precompile に不要なコードが実行速度を低下させていた 3分 15分 CDN へ sync Rails Server
freee エンジニア 、 相手 目を見ただけで sprockets をやめたい気持ちを伝える 特殊能力が発現していた
01 sprockets 戦歴 16 Section
sprockets を使わないフローにしたい front/* app/assets/* public/assets/* sprockets webpack が 直接 asset
生成する世界が目指したい世界 front/* public/assets/* CDN へ sync CDN へ sync 現在 目指す 未来
そ ために 数々 課題があった
L で説明する量で ない で(略)
歴代 雄姿 スライドで御覧ください • フロントエンド開発における革命とビルドプロセスについて - 2015/12 ◦ glup, webpack
導入 • ails ailから解放される始め 一歩 - 2016/01 ◦ webpck 導入 • フロントエンド モダン化とJava criptモジュール 依存解決 - 2016/06 ◦ sprockets require 削除 ◦ エントリーポイント 分割 ◦ glup, webpack 導入 • 大規模プロダクトにおけるフロントエンド 1年間 変化 - 2016/12 ◦ webpack ◦ webpack ビルド 高速化 • ailsアプリケーションにおけるフロントエンド環境 モダン化(人事労務フリー)
今回自分が立ち向かった
歴代が残した最後 命題
asset path 補完 問題 Rails Railから解放される始め 一歩
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 が分からない 当然埋め込めない 埋め込み 問題 生成 問題
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 書き換えが必要
asset path 補完 問題 Rails Railから解放される始め 一歩
現在 ビルド環境を壊すことなく webpack や ソースコード に手を入れる必要がある
並行運用できる構成(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
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 と互換性なし) 解決
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 を呼び出している
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
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
sprockets を使わないビルドフロー front/* app/assets/* public/assets/* sprockets webpack が 直接 asset
生成する世界へ到達? front/* public/assets/* CDN へ sync CDN へ sync 現在 目指す 未来
と言いたいところですが、いきなり こわい で
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
現在 目標 、一旦並行運用 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 で生成)
まとめ • 切り戻せる仕組み、安全に倒せる仕組みを常に考えておく ◦ 逆にそこだけ考えられれ 、どうにか進めていける • 並行運用して、安全を確認してから、次に進んでゆく ◦ 並行運用
ために 、既存 ビルドを通しつつ ◦ 新しいビルド 設定も動く環境を用意して進めいくと良さげ
爆速でデプロイされて 爆速で価値が提供できる世界へ 下地 整った
よく考えたら、 ails 話がほとんどになってしまった まあいっか
ありがとうございました