鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了 Webpacker 的運作方式,今天來實驗看看用 Webpacker 包 js + css + 其他資源檔,夠夠~
本篇會參考上一篇內容,並搭配 webpack RailsGuide 一起服用。
$ rails _6.1.6.1_ new test_webpacker
因為 rails 7 已經不預設使用 webpacker
了,就搬出 rails 6 來做這次的實驗
可以用 _x.x.x_
指令用特定版本,我用我本地有安裝的 6.1.6.1 版來創專案
$ which rails
,得到 rails 的路徑,我的路徑是在 /Users/unclesam/.rvm/gems/ruby-3.0.0/bin/rails
,會放在 rvm 的 ruby 版本底下,$ ls /Users/unclesam/.rvm/gems/ruby-3.0.0/gems | grep rails-
,列出符合 rails-
的 gems到 app/views/layouts/application.html.erb
看一下
<!DOCTYPE html>
<html>
<head>
<title>TestWebpacker</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
發現 rails 6 預設只用 webpacker
打包 javascript,css 還是留給 asset pipeline
,因為我們這次要做的實驗是要讓 webpacker 打包 js + css + 其他資源,所以我們手動把 stylesheet_link_tag
改成 stylesheet_pack_tag
,變成
<!DOCTYPE html>
<html>
<head>
<title>TestWebpacker</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
$ rails g controller home_controller index
到 config/routes.rb
把新增的 home controller 改成首頁
Rails.application.routes.draw do
root 'home#index'
end
根據我們昨天從 RailsGuide 看到的官方推薦的資料夾結構,來讓 webpacker 處理 css 吧
手動新增 app/javascript/stylesheets
資料夾
手動新增 app/javascript/stylesheets/title.scss
檔案,修改 h1 的背景顏色,作為測試用
// app/javascript/stylesheets/title.scss
h1 {
background-color: green;
}
手動新增 app/javascript/packs/application.scss
,引用 app/javascript/stylesheets/title.scss
// app/javascript/packs/application.scss
@import 'stylesheets/title'
$ rails s
,打開 127.0.0.1:3000
,確認一下 h1 有沒有變色
成功~
手動新增 app/javascript/src/click.js
// app/javascript/src/click.js
console.log("click.js is imported.")
export default function btnClick() {
console.log('clicked')
}
在 app/javascript/packs/application.js
把 click.js
import 近來
// app/javascript/packs/application.js
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
Rails.start()
Turbolinks.start()
ActiveStorage.start()
import btnClick from 'src/click'
試著在 app/views/home/index.html.erb
使用這個 btnClick
function
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<button onclick="btnClick()">按我</button>
一樣起 $ rail s
,再到 127.0.0.1:3000 確認點下去有沒有反應
發現確實是有 import 近來,但是並沒有辦法觸發 btnClick
我爬文後,發現驚人的事實,就是用 webpacker 打包後的 js,是不能被 view 的元件所使用的,只能透過 jquery 等套件,在 js 裡對元件操作!!!安捏甘賀?!有興趣的話,可以去看原文
所以如果要像之前一樣在 view 裡面去觸發 js function 的話,那就不能用 webpacker 來包,而是要回歸老路給 asset pipeline 來包了,我實在是沒有想到我會得到這樣的結論 XD
等於是跟之前的用法完全不一樣了,我們來試試看新的用法吧
$ yarn add jquery
在 app/javascript/packs/application.js
引用 jquery
// app/javascript/packs/application.js
..
import btnClick from 'src/click'
import $ from 'jquery'
取消原本 view 裡面 button 的 onclick event,然後給他一個 id
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<button id="test-btn">按我</button>
修改一下 app/javascript/src/click.js
的用法
// app/javascript/src/click.js
import $ from 'jquery';
console.log('click.js is imported.')
function btnClick() {
console.log('clicked')
}
$(document).ready(function() {
$('button#test-btn').on('click', function() {
btnClick()
})
})
啟動 $ rails s
,打開 127.0.0.1:3000
,檢查按按鈕後的行為
成功~
這種用法就是要告訴我們,以前 Rails 那套跟 js 互動的用法已經過時了,要換換我們的腦袋了 XD
如果不能接受這種用法的話,那就要回到 asset pipeline 的懷抱,可怕的是不知道到 rails 第幾版會被淘汰,所以還是要早早習慣新用法吧(好不想面對?
在 app/javascript/packs/application.js
新增引用 images 的路徑
// app/javascript/packs/application.js
..
const images = require.context("../images", true)
試著在 view 中顯示一張圖片
這邊要注意,路徑前面要加 media/
,沒有為什麼,就是 webpacker 在打包的規則
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<button id="test-btn">按我</button>
<%= image_pack_tag('media/images/test-img.jpeg', width: 300) %>
啟動 $ rails s
,打開 127.0.0.1:3000
,檢查圖片
成功~
靜態資源會被 webpacker 打包後,放在 public/packs/media
,在 RailsGuide 的下面這段有提到:
如果照 RailsGuide 所說的,靜態資源都會被包在 public/packs/media
裡,那麼字型應該也是一樣的道理,我們來實驗看看
我使用前陣子剛開源的****辰宇落雁體****作為示範
手動新增 app/javascript/fonts
資料夾,再將下載下來的 ChenYuluoyan-Thin.ttf
檔案放進去
到 app/javascript/packs/application.js
去引用 fonts 資料夾
// app/javascript/packs/application.js
...
const fonts = require.context("../fonts", true)
到 app/javascript/stylesheets/title.scss
來新增幾個字型到 p
tag
// app/javascript/stylesheets/title.scss
...
@font-face {
font-family: 'ChenYuluoyan';
src: asset_pack_tag('media/fonts/ChenYuluoyan-Thin.ttf');
}
p {
font-family: 'ChenYuluoyan';
}
這邊要用的已經不是 url
或 asset_url
囉,要改成 asset_pack_tag
,然後路徑前面記得要加 media
到 app/views/home/index.html.erb
新增一行中文字
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<p>2022 IT邦鐵人賽,讚拉</p>
<button id="test-btn">按我</button>
<%= image_pack_tag('media/images/test-img.jpeg', width: 300) %>
啟動 $ rails s
,打開 127.0.0.1:3000
,檢查字型
成功~~
$ RAILS_ENV=production rails assets:precompile
yarn install v1.22.18
[1/4] ? Resolving packages...
[2/4] ? Fetching packages...
[3/4] ? Linking dependencies...
[4/4] ? Building fresh packages...
✨ Done in 1.90s.
I, [2022-09-19T09:32:54.984809 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/manifest-b4bf6e57a53c2bdb55b8998cc94cd00883793c1c37c5e5aea3ef6749b4f6d92b.js
I, [2022-09-19T09:32:54.985005 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/manifest-b4bf6e57a53c2bdb55b8998cc94cd00883793c1c37c5e5aea3ef6749b4f6d92b.js.gz
I, [2022-09-19T09:32:54.985287 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.css
I, [2022-09-19T09:32:54.985578 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.css.gz
I, [2022-09-19T09:32:54.986196 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/home-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.css
I, [2022-09-19T09:32:54.986623 #20104] INFO -- : Writing /Users/unclesam/Projects/fullstack/test_webpacker/public/assets/home-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.css.gz
Compiling...
Compiled all packs in /Users/unclesam/Projects/fullstack/test_webpacker/public/packs
Hash: 09787e2b57bf0c4dafd5
Version: webpack 4.46.0
Time: 18115ms
Built at: 2022/09/19 上午9:33:14
Asset Size Chunks Chunk Names
css/application-34a11b4c.css 145 bytes 0 [emitted] [immutable] application
css/application-34a11b4c.css.br 90 bytes [emitted]
js/application-423c2e2e62674d7f7346.js 157 KiB 0 [emitted] [immutable] application
js/application-423c2e2e62674d7f7346.js.LICENSE.txt 493 bytes [emitted]
js/application-423c2e2e62674d7f7346.js.br 42.1 KiB [emitted]
js/application-423c2e2e62674d7f7346.js.gz 47.4 KiB [emitted]
js/application-423c2e2e62674d7f7346.js.map 666 KiB 0 [emitted] [dev] application
js/application-423c2e2e62674d7f7346.js.map.br 160 KiB [emitted]
js/application-423c2e2e62674d7f7346.js.map.gz 187 KiB [emitted]
manifest.json 712 bytes [emitted]
manifest.json.br 253 bytes [emitted]
manifest.json.gz 281 bytes [emitted]
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf 4.3 MiB [emitted] [big]
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf.br 2.46 MiB [emitted] [big]
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf.gz 2.79 MiB [emitted] [big]
media/images/test-img-f31c80b96be41efe81d2a83ae74e97a2.jpeg 157 KiB [emitted]
Entrypoint application = css/application-34a11b4c.css js/application-423c2e2e62674d7f7346.js js/application-423c2e2e62674d7f7346.js.map
[1] ./app/javascript/images/test-img.jpeg 105 bytes {0} [optional] [built]
[5] multi ./app/javascript/packs/application.js ./app/javascript/packs/application.scss 40 bytes {0} [built]
[6] ./app/javascript/channels/index.js 205 bytes {0} [built]
[7] ./app/javascript/channels sync _channel\.js$ 160 bytes {0} [built]
[8] ./app/javascript/images sync ^\.\/.*$ 195 bytes {0} [built]
[9] ./app/javascript/fonts sync ^\.\/.*$ 188 bytes {0} [built]
[10] ./app/javascript/fonts/ChenYuluoyan-Thin.ttf 112 bytes {0} [optional] [built]
[11] ./app/javascript/packs/application.scss 39 bytes {0} [built]
[12] ./app/javascript/packs/application.js + 1 modules 839 bytes {0} [built]
| ./app/javascript/packs/application.js 622 bytes [built]
| ./app/javascript/src/click.js 192 bytes [built]
+ 5 hidden modules
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf (4.3 MiB)
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf.gz (2.79 MiB)
media/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf.br (2.46 MiB)
WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!app/javascript/packs/application.scss:
Entrypoint mini-css-extract-plugin = *
[0] ./node_modules/css-loader/dist/cjs.js??ref--6-1!./node_modules/postcss-loader/src??ref--6-2!./node_modules/sass-loader/dist/cjs.js??ref--6-3!./app/javascript/packs/application.scss 836 bytes {0} [built]
+ 1 hidden module
從打包的 log 看起來,有看到 js, css, font, image 的身影,應該是都有包進去,然後還有看到警告字型的檔案太大 XD,我先當作沒看到?
到 public/
裡的各個資料夾檢查一下,是不是該包的都有在該放的位置
沒錯,public/assets
放的是 asset pipeline 打包的,但我們這測試用專案沒有留東西給 asset pipeline 打包,所以打包出來裡面應該是空的,再來 public/packs
放的就是 webpacker 包的,分別有 css, js, fonts, images,然後 fonts 和 images 都是放在 media 底下,那就沒問題了
以 production 環境啟動 $ RAILS_ENV=production rails s
,打開 127.0.0.1:3000
,檢查畫面
失敗 QQ
看一下錯誤訊息,會發現是在 http://127.0.0.1:3000/packs/
路徑下找不到各自的檔案,回憶了一下,想起來這個問題我有經驗!
在 config/environments/production.rb
設定檔中,有一行 config.public_file_server.enabled
的設定,仔細看了一下它的註解,會發現這個設定就是關於 public 資料夾對於靜態資源檔的服務,預設是 false
的,就代表 public 資料夾內的靜態資源檔是不會被處理的,也就是不能透過 127.0.0.1:3000/packs/xxx
等路徑去取得資源檔
require "active_support/core_ext/integer/time"
Rails.application.configure do
...
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
...
end
解決方法就是設定 RAILS_SERVE_STATIC_FILES
這個環境變數,讓它不要是空的值就好
於是我們重新以 production 環境、並帶著環境變數來啟動 $ RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true rails s
,打開 127.0.0.1:3000
,檢查畫面
這時候整個畫面就正常了!真是太神奇了山姆
最後我們來檢查一下瀏覽器讀取的資源是怎麼讀取的
<head>
裡面的 <script src="/packs/js/application-423c2e2e62674d7f7346.js" data-turbolinks-track="reload"></script>
這行,會發現讀取路徑是抓 /packs/js/application-xxx.js
,對應到的就是 public/packs/js/application-xxx.js
打包後的檔案,所以沒問題~<head>
裡面的 <link rel="stylesheet" media="all" href="/packs/css/application-34a11b4c.css" data-turbolinks-track="reload">
這行,會發現讀取路徑是抓 /packs/css/application-xxx.css
,對應到的就是 public/packs/css/application-xxx.css
打包後的檔案,所以也沒問題~<img>
裡面的 <img width="300" src="/packs/media/images/test-img-f31c80b96be41efe81d2a83ae74e97a2.jpeg">
這行,會發現讀取路徑是抓 /packs/media/images/xxx.jpeg
,對應到的就是 public/packs/media/images/xxx.jpeg
打包後的檔案,所以也沒問題~[http://127.0.0.1:3000/packs/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf](http://127.0.0.1:3000/packs/fonts/ChenYuluoyan-Thin-fbbc56c60f5b066a2d56734dedb45f8f.ttf)
是可以下載到字型檔的,但是在瀏覽器工具的 網路 分頁,卻沒有看到載入字型檔的紀錄,我以為字型也是透過網路傳送的,如果有大神知道在留言分享一下,感謝從這次的實驗,確實驗證了 RailsGuide webpacker 講的,不過讓我意外的是 js 的使用方法不一樣了,這樣 Rails 的前後端就更分離了,前端部分寫起來更”前端”。
透過這次實驗也更瞭解 webpacker 的打包邏輯,還有各種資源檔打包後的使用方式,我的 Rails 前端之路又邁進了一個里程碑,真開心 XD
那今天就先這樣囉,我們明天見~