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
28k
開発速度を支える技術 / The Techniques for Keeping The Development Speed
him0
0
3.2k
Other Decks in Technology
See All in Technology
Amazon FSx for NetApp ONTAPを利用するにあたっての要件整理と設計のポイント
non97
1
130
Sidekiq vs Solid Queue
willnet
11
7k
CI/CDやテスト自動化の開発プロジェクトへの適用
megascus
3
650
GitHub Universe: Evaluating RAG apps in GitHub Actions
pamelafox
0
130
来年もre:Invent2024 に行きたいあなたへ - “集中”と“つながり”で楽しむ -
ny7760
0
110
【LT】ソフトウェア産業は進化しているのか? -Javaの想い出とともに- #jjug_ccc
takabow
0
150
バイセルにおけるAI活用の取り組みについて紹介します/Generative AI at BuySell Technologies
kyuns
1
200
ActiveRecord SQLインジェクションクイズ (Rails 7.1.3.4)
kozy4324
9
2.1k
なんで、私がAWS Heroに!? 〜社外の広い世界に一歩踏み出そう〜
minorun365
PRO
1
540
新R25、乃木坂46 Mobileなどのファンビジネスを支えるマルチテナンシーなプラットフォームの全体像 / cam-multi-cloud
cyberagentdevelopers
PRO
1
110
リファクタリングへの耐性が高いモデルベースの統合テストの紹介 / Model-Base Integration Test for Refactoring
yuitosato
5
1.5k
DFTの実践的基礎理論
pfn
PRO
2
100
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
5
140
Into the Great Unknown - MozCon
thekraken
31
1.5k
Learning to Love Humans: Emotional Interface Design
aarron
272
40k
GraphQLとの向き合い方2022年版
quramy
43
13k
The Pragmatic Product Professional
lauravandoore
31
6.3k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Code Review Best Practice
trishagee
64
17k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
355
29k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Faster Mobile Websites
deanohume
304
30k
Unsuck your backbone
ammeep
668
57k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
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 話がほとんどになってしまった まあいっか
ありがとうございました