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
.NET 9 のパフォーマンス改善
nenonaninu
0
900
マルチプロダクト開発の現場でAWS Security Hubを1年以上運用して得た教訓
muziyoshiz
3
2.3k
re:Invent をおうちで楽しんでみた ~CloudWatch のオブザーバビリティ機能がスゴい!/ Enjoyed AWS re:Invent from Home and CloudWatch Observability Feature is Amazing!
yuj1osm
0
120
How to be an AWS Community Builder | 君もAWS Community Builderになろう!〜2024 冬 CB募集直前対策編?!〜
coosuke
PRO
2
2.8k
あの日俺達が夢見たサーバレスアーキテクチャ/the-serverless-architecture-we-dreamed-of
tomoki10
0
460
LINE Developersプロダクト(LIFF/LINE Login)におけるフロントエンド開発
lycorptech_jp
PRO
0
120
日本版とグローバル版のモバイルアプリ統合の開発の裏側と今後の展望
miichan
1
130
社外コミュニティで学び社内に活かす共に学ぶプロジェクトの実践/backlogworld2024
nishiuma
0
260
AI時代のデータセンターネットワーク
lycorptech_jp
PRO
1
290
第3回Snowflake女子会_LT登壇資料(合成データ)_Taro_CCCMK
tarotaro0129
0
190
MLOps の現場から
asei
6
640
Opcodeを読んでいたら何故かphp-srcを読んでいた話
murashotaro
0
240
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
51
7.3k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
A Tale of Four Properties
chriscoyier
157
23k
It's Worth the Effort
3n
183
28k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Building Applications with DynamoDB
mza
91
6.1k
Statistics for Hackers
jakevdp
796
220k
Visualization
eitanlees
146
15k
Code Reviewing Like a Champion
maltzj
520
39k
Unsuck your backbone
ammeep
669
57k
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 話がほとんどになってしまった まあいっか
ありがとうございました