iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Modern Web

Rails,我要進來囉系列 第 12

第十二天:實驗用 webpacker 打包 js+css+font+image 資源檔

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了 Webpacker 的運作方式,今天來實驗看看用 Webpacker 包 js + css + 其他資源檔,夠夠~

本篇會參考上一篇內容,並搭配 webpack RailsGuide 一起服用。

創個專門實驗用的新專案

$ rails _6.1.6.1_ new test_webpacker

因為 rails 7 已經不預設使用 webpacker 了,就搬出 rails 6 來做這次的實驗

如果本地有安裝多種版本的 rails

可以用 _x.x.x_ 指令用特定版本,我用我本地有安裝的 6.1.6.1 版來創專案

如果你要檢查本地有安裝哪些版本的 rails

  1. 可以先用 $ which rails,得到 rails 的路徑,我的路徑是在 /Users/unclesam/.rvm/gems/ruby-3.0.0/bin/rails,會放在 rvm 的 ruby 版本底下,
  2. 然後再到 $ ls /Users/unclesam/.rvm/gems/ruby-3.0.0/gems | grep rails-,列出符合 rails- 的 gems
  3. 再看一下有哪些版本的 rails 就好

檢查一下 Rails 6 預設的 webpacker

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>

開個 view,作為我們的主測試戰場

  1. $ rails g controller home_controller index

  2. config/routes.rb 把新增的 home controller 改成首頁

    Rails.application.routes.draw do
      root 'home#index'
    end
    

用 Webpacker 打包 CSS

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-1.png

根據我們昨天從 RailsGuide 看到的官方推薦的資料夾結構,來讓 webpacker 處理 css 吧

  1. 手動新增 app/javascript/stylesheets 資料夾

  2. 手動新增 app/javascript/stylesheets/title.scss 檔案,修改 h1 的背景顏色,作為測試用

    // app/javascript/stylesheets/title.scss
    h1 {
        background-color: green;
    }
    
  3. 手動新增 app/javascript/packs/application.scss,引用 app/javascript/stylesheets/title.scss

    // app/javascript/packs/application.scss
    @import 'stylesheets/title'
    
  4. $ rails s,打開 127.0.0.1:3000,確認一下 h1 有沒有變色

  5. 成功~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-2.png

用 Webpacker 打包 JS - Round 1(失敗QQ)

  1. 手動新增 app/javascript/src/click.js

    // app/javascript/src/click.js
    console.log("click.js is imported.")
    
    export default function btnClick() {
      console.log('clicked')
    }
    
  2. app/javascript/packs/application.jsclick.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'
    
  3. 試著在 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>
    
  4. 一樣起 $ rail s,再到 127.0.0.1:3000 確認點下去有沒有反應

  5. 發現確實是有 import 近來,但是並沒有辦法觸發 btnClick

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-3.png

我爬文後,發現驚人的事實,就是用 webpacker 打包後的 js,是不能被 view 的元件所使用的,只能透過 jquery 等套件,在 js 裡對元件操作!!!安捏甘賀?!有興趣的話,可以去看原文

所以如果要像之前一樣在 view 裡面去觸發 js function 的話,那就不能用 webpacker 來包,而是要回歸老路給 asset pipeline 來包了,我實在是沒有想到我會得到這樣的結論 XD

等於是跟之前的用法完全不一樣了,我們來試試看新的用法吧

用 Webpacker 打包 JS - round 2 (成功~)

  1. $ yarn add jquery

  2. app/javascript/packs/application.js 引用 jquery

    // app/javascript/packs/application.js
    ..
    
    import btnClick from 'src/click'
    import $ from 'jquery'
    
  3. 取消原本 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>
    
  4. 修改一下 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()
      })
    })
    
  5. 啟動 $ rails s,打開 127.0.0.1:3000,檢查按按鈕後的行為

  6. 成功~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-4.png

這種用法就是要告訴我們,以前 Rails 那套跟 js 互動的用法已經過時了,要換換我們的腦袋了 XD

如果不能接受這種用法的話,那就要回到 asset pipeline 的懷抱,可怕的是不知道到 rails 第幾版會被淘汰,所以還是要早早習慣新用法吧(好不想面對?

用 webpacker 打包 image

  1. app/javascript/packs/application.js 新增引用 images 的路徑

    // app/javascript/packs/application.js
    
    ..
    const images = require.context("../images", true)
    
  2. 試著在 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) %>
    
  3. 啟動 $ rails s,打開 127.0.0.1:3000,檢查圖片

  4. 成功~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-5.png

靜態資源會被 webpacker 打包後,放在 public/packs/media,在 RailsGuide 的下面這段有提到:

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-6.png

用 webpacker 打包字型

如果照 RailsGuide 所說的,靜態資源都會被包在 public/packs/media 裡,那麼字型應該也是一樣的道理,我們來實驗看看

  1. 我使用前陣子剛開源的****辰宇落雁體****作為示範

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-7.png

  2. 手動新增 app/javascript/fonts 資料夾,再將下載下來的 ChenYuluoyan-Thin.ttf 檔案放進去

  3. app/javascript/packs/application.js 去引用 fonts 資料夾

    // app/javascript/packs/application.js
    
    ...
    const fonts = require.context("../fonts", true)
    
  4. 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';
    }
    

    這邊要用的已經不是 urlasset_url 囉,要改成 asset_pack_tag,然後路徑前面記得要加 media

  5. 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) %>
    
  6. 啟動 $ rails s,打開 127.0.0.1:3000,檢查字型

  7. 成功~~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-8.png

嘗試以 production 環境來驗證打包結果!

  1. $ 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,我先當作沒看到?

  2. public/ 裡的各個資料夾檢查一下,是不是該包的都有在該放的位置

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-9.png

    沒錯,public/assets 放的是 asset pipeline 打包的,但我們這測試用專案沒有留東西給 asset pipeline 打包,所以打包出來裡面應該是空的,再來 public/packs 放的就是 webpacker 包的,分別有 css, js, fonts, images,然後 fonts 和 images 都是放在 media 底下,那就沒問題了

  3. 以 production 環境啟動 $ RAILS_ENV=production rails s,打開 127.0.0.1:3000,檢查畫面

  4. 失敗 QQ

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-10.png

    看一下錯誤訊息,會發現是在 http://127.0.0.1:3000/packs/ 路徑下找不到各自的檔案,回憶了一下,想起來這個問題我有經驗!

  5. 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 這個環境變數,讓它不要是空的值就好

  6. 於是我們重新以 production 環境、並帶著環境變數來啟動 $ RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true rails s,打開 127.0.0.1:3000,檢查畫面

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day12-11.png

    這時候整個畫面就正常了!真是太神奇了山姆

  7. 最後我們來檢查一下瀏覽器讀取的資源是怎麼讀取的

    1. js 的讀取方法:看 <head> 裡面的 <script src="/packs/js/application-423c2e2e62674d7f7346.js" data-turbolinks-track="reload"></script> 這行,會發現讀取路徑是抓 /packs/js/application-xxx.js,對應到的就是 public/packs/js/application-xxx.js 打包後的檔案,所以沒問題~
    2. css 的讀取方法:看 <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 打包後的檔案,所以也沒問題~
    3. image 的讀取方法:看 <img> 裡面的 <img width="300" src="/packs/media/images/test-img-f31c80b96be41efe81d2a83ae74e97a2.jpeg"> 這行,會發現讀取路徑是抓 /packs/media/images/xxx.jpeg,對應到的就是 public/packs/media/images/xxx.jpeg 打包後的檔案,所以也沒問題~
    4. font 的讀取方法:從 [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

那今天就先這樣囉,我們明天見~


上一篇
第十一天:關於 Webpacker 打包那回事
下一篇
第十三天:關於 Asset Pipeline 打包那回事
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言