Slide 1

Slide 1 text

freee 株式会社
 やっと sprockets やめる話
 2019.10.04 Gotanda.js #13

Slide 2

Slide 2 text

● 経歴
 ○ 2017〜 freee 会計チーム 人
 ● 触ってるも 
 ○ eb フロント Java cript, Flow
 ○ uby on ails
 ○ マイクロサービス golang
 ○ CI/CD, docker, Kubernetes
 ● 最近
 ○ シンフォギア
 ■ 最終回良かった
 ■ 7年間 締めくくりとして最高だった
 ひも Twitter @him0net him0
 2

Slide 3

Slide 3 text

freee と sprockets 戦い 歴史 長い
 そんな、戦いに終止符を打つ


Slide 4

Slide 4 text

打ちたいなー


Slide 5

Slide 5 text

そんな話です


Slide 6

Slide 6 text

freee 株式会社
 やっと sprockets やめる話
 2019.10.04 Gotanda.js #13

Slide 7

Slide 7 text

00 会計freee sprockets
 10 Section

Slide 8

Slide 8 text

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 コンパイルを行うことも可能

Slide 9

Slide 9 text

prockets ails - asset_path
 <%= stylesheet_include_tag 'summary' %> <%= javascript_include_tag 'summary' %> app/assets/views/summary/index.erb 
 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 埋め込み


Slide 10

Slide 10 text

. 現状 prockets ってそもそも必要な ?
 フロントエンドビルド環境 充実により
 邪魔くさい存在になってきた
 sprockets
 
 gem 追加でライブラリ 埋め込みや alt CSS や alt JS コンパイルを行うことも可能 フロントエンド 更新に gem 更新 
 をまたなけれ ならない問題 
 webpack と 併用による 
 assets パイプライン 複雑化 問題 
 front/javascripts/* app/assets/javascripts/* public/assets/javascripts/* sprockets
 alt JS で開発 一旦 js で 吐き出す webpacker も同様な動作が できるが、独自 依存が多 く、入れるメリットが感じられ ない で入れていない

Slide 11

Slide 11 text

複雑でかつ遅いビルドフロー
 front/* app/assets/* public/assets/* sprockets
 ごめんwww 18分かかるわwww ごめんごwww Rails 全体を読み込んで初期化 会計 freee コードベースがでかすぎて低速化 (他 原因もある かもしれないが深追いせず) precompile に不要なコードが実行速度を低下させていた
 3分
 15分
 CDN へ sync Rails Server

Slide 12

Slide 12 text

freee エンジニア 、
 相手 目を見ただけで
 sprockets をやめたい気持ちを伝える
 特殊能力が発現していた


Slide 13

Slide 13 text

01 sprockets 戦歴
 16 Section

Slide 14

Slide 14 text

sprockets を使わないフローにしたい
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset 生成する世界が目指したい世界
 front/* public/assets/* CDN へ sync CDN へ sync 現在
 目指す
 未来


Slide 15

Slide 15 text

そ ために 数々 課題があった


Slide 16

Slide 16 text

L で説明する量で ない で(略)


Slide 17

Slide 17 text

歴代 雄姿 スライドで御覧ください
 ● フロントエンド開発における革命とビルドプロセスについて - 2015/12
 ○ glup, webpack 導入
 ● ails ailから解放される始め 一歩 - 2016/01
 ○ webpck 導入
 ● フロントエンド モダン化とJava criptモジュール 
依存解決 - 2016/06
 ○ sprockets require 削除
 ○ エントリーポイント 分割
 ○ glup, webpack 導入
 ● 大規模プロダクトにおけるフロントエンド 1年間 変化 - 2016/12
 ○ webpack
 ○ webpack ビルド 高速化
 ● ailsアプリケーションにおけるフロントエンド環境 モダン化(人事労務フリー)


Slide 18

Slide 18 text

今回自分が立ち向かった 


Slide 19

Slide 19 text

歴代が残した最後 命題


Slide 20

Slide 20 text

asset path 補完 問題
 Rails Railから解放される始め 一歩 


Slide 21

Slide 21 text

prockets ails asset_path を使えない
 <%= stylesheet_include_tag 'summary' %> <%= javascript_include_tag 'summary' %> app/assets/views/summary/index.erb 
 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 が分からない
 当然埋め込めない
 埋め込み 問題 生成 問題

Slide 22

Slide 22 text

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 書き換えが必要

Slide 23

Slide 23 text

asset path 補完 問題
 Rails Railから解放される始め 一歩 


Slide 24

Slide 24 text

現在 ビルド環境を壊すことなく
 webpack や ソースコード に手を入れる必要がある


Slide 25

Slide 25 text

並行運用できる構成(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

Slide 26

Slide 26 text

prockets ails asset_path を使えない
 <%= stylesheet_include_tag 'summary' %> <%= javascript_include_tag 'summary' %> app/assets/views/summary/index.erb 
 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 と互換性なし)
 解決


Slide 27

Slide 27 text

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' asset_path 'javascripts/summary.js' "/assets/stylesheets/summary.xxx.js" 内部的に asset_path 
 を呼び出している


Slide 28

Slide 28 text

asset_path 置き換え
 ebpackAssetHelper
 ● asset_path_from_manifest
 
 ● javascript_bundle_tag
 ● stylesheet_bundle_tag
 ● favicon_bundle_tag
 ● image_bundle_tag
 javascript_bundle_tag 'summary' 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


Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

sprockets を使わないビルドフロー
 front/* app/assets/* public/assets/* sprockets
 webpack が 直接 asset 生成する世界へ到達?
 front/* public/assets/* CDN へ sync CDN へ sync 現在
 目指す
 未来


Slide 31

Slide 31 text

と言いたいところですが、いきなり こわい で


Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

現在 目標 、一旦並行運用
 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 で生成) 


Slide 34

Slide 34 text

まとめ
 ● 切り戻せる仕組み、安全に倒せる仕組みを常に考えておく
 ○ 逆にそこだけ考えられれ 、どうにか進めていける
 ● 並行運用して、安全を確認してから、次に進んでゆく
 ○ 並行運用 ために 、既存 ビルドを通しつつ
 ○ 新しいビルド 設定も動く環境を用意して進めいくと良さげ


Slide 35

Slide 35 text

爆速でデプロイされて
 爆速で価値が提供できる世界へ
 下地 整った 


Slide 36

Slide 36 text

よく考えたら、 ails 話がほとんどになってしまった 
 まあいっか


Slide 37

Slide 37 text

ありがとうございました