Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
やっと sprockets やめる話 / goodbye sprockets
Search
Hiroki Nakashima
October 04, 2019
Technology
30k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
やっと sprockets やめる話 / goodbye sprockets
freee と sprockets の戦いの歴史は長い、その戦いについに終止符を打つときが来たという話です。
Hiroki Nakashima
October 04, 2019
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
34k
開発速度を支える技術 / The Techniques for Keeping The Development Speed
him0
0
3.9k
Other Decks in Technology
See All in Technology
その Lambda、8分で 管理者権限まで奪われます
k1nakayama
1
100
映像変換サーバーなしで端末内でHLSを生成してライブ配信
hikarusato
0
110
Snowflakeのコスト最適化を支えるアーキテクチャ設計
ktatsuya
1
1.6k
DEFCON_CHV_CTF_Write-up.pdf
bata_24
0
150
俺の仕事は AIに奪われないし、たぶんその BIも要らない
hikaruri
0
490
MCPをつなげて作る組織横断のAIエージェント基盤(の開発工程)
tsubakimoto_s
0
140
山手線を徒歩で一周してわかった、 位置情報アプリは「足」が最強のデバッガー
hinakko
0
160
顧客に向き合う開発組織へ。リアーキテクチャとフィーチャーチーム化で挑む組織改革
safie
0
1.9k
What the customer really needed
kawaguti
PRO
2
180
越境するなら専門用語を使うな高校校歌 / If you wanna cross border, you shouldn't use jargon
vtryo
0
140
CLIライブラリ開発を支える技術
htnabe
0
120
開発投資の期待値を上げるプロダクトロードマップづくり ~プロダクトエンジニアが越境して事業を伸ばす~
kekekenta
1
180
Featured
See All Featured
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.8k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
330
Become a Pro
speakerdeck
PRO
31
6.3k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.2k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Claude Code のすすめ
schroneko
67
230k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.5k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
67
58k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Testing 201, or: Great Expectations
jmmastey
46
8.3k
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 話がほとんどになってしまった まあいっか
ありがとうございました