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.8k
やっと 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
33k
開発速度を支える技術 / The Techniques for Keeping The Development Speed
him0
0
3.3k
Other Decks in Technology
See All in Technology
「AI駆動開発」のボトルネック『言語化』を効率化するには
taniiicom
1
230
AI コードレビューが面倒すぎるのでテスト駆動開発で解決しようとして読んだら、根本的に俺の勘違いだった
mutsumix
0
110
増え続ける脆弱性に立ち向かう: 事前対策と優先度づけによる 持続可能な脆弱性管理 / Confronting the Rise of Vulnerabilities: Sustainable Management Through Proactive Measures and Prioritization
nttcom
1
230
Microsoft Learn MCP/Fabric データエージェント/Fabric MCP/Copilot Studio-簡単・便利なAIエージェント作ってみた -"Building Simple and Powerful AI Agents with Microsoft Learn MCP, Fabric Data Agent, Fabric MCP, and Copilot Studio"-
reireireijinjin6
1
190
完璧を目指さない小さく始める信頼性向上
kakehashi
PRO
0
120
Mambaで物体検出 完全に理解した
shirarei24
2
120
経理出身PdMがAIプロダクト開発を_ハンズオンで学んだ話.pdf
shunsukenarita
1
260
マルチモーダル基盤モデルに基づく動画と音の解析技術
lycorptech_jp
PRO
2
290
Perlアプリケーションで トレースを実装するまでの 工夫と苦労話
masayoshi
0
190
株式会社島津製作所_研究開発(集団協業と知的生産)の現場を支える、OSS知識基盤システムの導入
akahane92
1
1.3k
生成AIによる情報システムへのインパクト
taka_aki
1
210
Kiro Hookを Terraformで検証
ao_inoue
0
140
Featured
See All Featured
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Navigating Team Friction
lara
188
15k
A better future with KSS
kneath
238
17k
Six Lessons from altMBA
skipperchong
28
3.9k
Designing for Performance
lara
610
69k
Git: the NoSQL Database
bkeepers
PRO
431
65k
[RailsConf 2023] Rails as a piece of cake
palkan
56
5.7k
Statistics for Hackers
jakevdp
799
220k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
130
19k
Agile that works and the tools we love
rasmusluckow
329
21k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
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 話がほとんどになってしまった まあいっか
ありがとうございました