如何在 Rails 使用 Webpacker(中)

如何在 Rails 使用 Webpacker(中)
credit: Rubén Bagüés

前一篇文章介紹了如何在 Rails 使用 Webpacker,接下來這篇就我們就來實際安裝幾個 JavaScript 套件來試試手感吧!

安裝 jQuery

在各種前端框架橫行的現在,jQuery 這種看起來相對比較樸實無華且枯燥的 JS 套件就好像沒什麼人想用了。在 Rails 5.1.0 的時候,DHH 把以往一直預設安裝的 jQuery 給拿掉了,所以如果想要使用 jQuery 的話就得自己再手動裝回來,通常會使用 jquery-rails 這個套件。

即然都拔掉了為什麼還會想裝回來?因為還是有一些套件是依賴 jQuery 在做事的,例如在下一篇文章會介紹到的 Twitter Bootstrap 就是其中一個。

接下來我們就試著使用 Webpacker 來把 jQuery 裝回來吧!

Step1: 安裝 jQuery

這裡我們不使用 Gem,而是使用 yarn 來安裝:

$ yarn add --dev jquery

這個指令執行完成後,會自動在根目錄的 package.json 加上 jQuery 相關的設定:

{
  "name": "cc4",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "^4.0.7",
    "jquery": "^3.4.1"
  },
  "devDependencies": {
    "webpack-dev-server": "^3.9.0"
  }
}

同時也會把 jQuery 套件放一份在專案裡的 node_modules 目錄裡。

Step2: 引用 jQuery

如同前面介紹的做法,如果 jQuery 想要全站都可以使用的話,可以直接把 jQuery 在 app/javascript/pack/application.js 裡把它引用進來:

// 檔案:app/javascript/packs/application.js

import '../ccc'
import 'jquery'

跟上一篇介紹到的 import '../ccc' 的相對目錄寫法不同,這裡的 import 'jquery' 會去 node_modules 目錄裡去找 jQuery,這樣就可以把 jQuery 引用進來了。讓我們來試一下:

// 檔案:app/javascript/ccc/smile.js

import 'jquery'

$().ready(function(){
  console.log("You're so sweet!")
})

寫過 jQuery 的人應該都看過這個 $ 符號是 jQuery 的招牌符號,$().ready() 則是在 DOM 載入完成之後執行裡面的那個 callback function,所以理論上上面這幾行應該可以在瀏覽器的 console 裡看到 You're so sweet! 的字樣,但打開瀏覽器執行一下,發現這個訊息:

Uncaught ReferenceError: $ is not defined

咦?以前不是直接加上 <script src="..."></script> 就可以用了嗎?現在怎麼不行了?就,以前你是直接用別人打包好的檔案,現在你是自己打包啊。這有幾種解決方法,第一種,就是直接在 import 的時候把 $ 明白的寫出來:

// 檔案:app/javascript/ccc/smile.js

import $ from 'jquery'

$().ready(function(){
  console.log("You're so sweet!")
})

這樣就有 $ 符號可以用了。另一種做法,就是直接在 Webpack 的環境設定檔上動手腳,讓 $ 變成全站都能用:

// 檔案:config/webpack/environment.js

const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery'
  })
)

module.exports = environment

透過 Webpack 的 ProvidePlugin 來自動幫我們載入模組,就不用自己手動 importrequire 了。

參考資料:https://webpack.js.org/plugins/provide-plugin/

改了這個設定檔,記得重開 foreman(或 rails server)才會生效。這樣一來即使沒有寫 import 'jquery',也可以直接使用 $ 符號了。

順利引入 jQuery 之後,讓我們繼續來 View(也就是 HTML 的頁面)寫一小段 jQuery:

// 檔案:app/views/pages/home.html.erb

<h1>Cute App</h1>
<p>You're too smart to understand my code!</p>

<script>
  $().ready(function(){
    console.log('Make jQuery AWESOME Again')
  })
</script>

這應該也是很常見的寫法,就是直接在 HTML 頁面裡寫 JavaScript,我們暫且先不討論在 2019 年還這樣寫是不是適當,但重新整理之後,發現...咦?怎麼發生錯誤了:

Uncaught ReferenceError: $ is not defined

又是 not defined!不是 jQuery 有安裝了嗎?而且剛剛在 app/javascript/ccc/smile.js 裡寫的時候在 Webpack 編譯的過程也沒事?為什麼這個 $ 又會沒定義?

Step 3: 設定 $ 符號

原本我也以為天真的以為這些 JavaScript 套件就只要 import 進來就沒事了,但看起來沒這麼單純。

前端世界真複雜 Orz

原來,的確是引入了 jQuery 沒錯,但那個 $ 符號並不會就這樣生效,如果去翻一下 jQuery 原始碼的最後幾行是這樣寫的:

    if ( !noGlobal ) {
      window.jQuery = window.$ = jQuery;
    }

資料來源:https://code.jquery.com/jquery-3.4.1.js

簡單的說,就是如果使用 Webpack 打包的情境下是沒有瀏覽器的 window 物件的,所以自然就沒有 $ 或是 jQuery 這幾個全域變數,所以在執行到上面那段程式碼的時候才會出現 $ is not defined 的錯誤訊息。

那該怎麼辦?即然它沒有做 $,那我們就自己手動自己做。

    // 檔案:app/javascript/packs/application.js

    import '../ccc'

    window.jQuery = $
    window.$ = $

剛剛在前面透過 Webpack 的 ProvidePlugin$ 變全域變數,在這邊再手動把它塞給 window,讓它在 View 也可以正常使用,這樣剛剛的 $ not defined 的問題就可以解決了。

只是,在前端越來越複雜的情況下,應該也越來越少人會在 HTML 裡塞 JavaScript 了才是?不過如果你還是有這個需求的話,上面這樣手動把 $ 設定到 window 物件也是一個解法。

今日是何日之 Date Picker

看完了 jQuery,我們來試個漂亮一點的小玩意兒吧!這次來試個可以在網頁上點選日期的小工具:flatpickr

點開網站說明,雖然它可以用 CDN 的方式安裝,但我們就來練習把它一併打包到我們的專案裡試試看。

Step 1:安裝 flatpickr

起手式都差不多,都是用 yarnnpm 把套件拉回來:

$ yarn add --dev flatpickr

這個指令除了把套件拉回 node_modules 之外,同樣也會自動修改 package.json 的內容。

Step 2:串接 JavaScript

根據手冊上的記載,它的使用方法是這樣:

    import flatpickr from "flatpickr"

這行看起來應該有點熟悉吧!接著我在專案的 app/javascript/ 目錄裡建立一個名為 utils 的目錄,並在裡面新增一個名為 datepicker.js 的檔案,內容如下:

    // 檔案:app/javascript/utils/datepicker.js

    import flatpickr from 'flatpickr'

    document.addEventListener('DOMContentLoaded', function(){
      flatpickr("#theCutePicker", {})
    })

這裡為什麼要取名 utils 目錄以及 datepicker.js 檔案?其實沒什麼特別的原因,就只是不想把程式都寫在同一個檔案裡而已 :)

透過 import 語法把 flatpickr 套件引入,根據手冊的說明,只要呼叫 flatpickr 函數並把指定的 DOM 元件的 id(或 class)名稱傳給它就可以正常運作了,所以,接下來我在頁面上新增一個 id 叫做 theCutePickerinput 元素:

    // 檔案:app/views/pages/home.html.erb

    <h1>Cute App</h1>
    <p>You're too smart to understand my code!</p>

    <input type="text" id="theCutePicker">

這時候畫面上除了一個輸入框之外,應該還沒什麼變化,因為雖然剛剛好像在 utils/datepicker.js 檔案裡引入了 flatpickr,但還沒讓它引進整個專案,所以接下來:

    // 檔案:app/javascript/packs/application.js

    import '../ccc'
    import '../utils/datepicker'

這時再回到瀏覽器應該會發現畫面有些變化了,但看起來不像是我們期望的效果啊!畫面上出現大大的箭頭、日期跟星期都跑到不對的地方,看了一下 Console 並沒有 JavaScript 的錯誤,應該是 CSS 沒設定的樣子。

是的,就是這樣,一樣再翻了一下手冊的說明,應該還要有一個 flatpickr.min.css 需要一併引入。其實這個 CSS 檔也一併在安裝的時候拉回 node_modules 裡了:

flatpickr

這個 CSS 檔就放在 node_modules/flatpickr/dist 目錄裡,即然知道在哪裡就簡單了!讓我們回到 datepicker.js 檔案加上一行:

    // 檔案:app/javascript/utils/datepicker.js

    import flatpickr from 'flatpickr'
    import 'flatpickr/dist/flatpickr.min.css'

    document.addEventListener('DOMContentLoaded', function(){
      flatpickr("#theDatePicker", {})
    })

就這樣把它 import 進來,然後再回到瀏覽器看看:

Datepickr

搞定,瞧瞧這精美的 Date Picker!

等等...好像哪裡怪怪的,為什麼剛剛是在一個 JavaScript 檔案裡面 import 一個 CSS 檔案?可以這樣嗎?

這個嘛,其實對 Webpack 來說,所有的東西都是 JavaScript,而這也是下一篇文章要再說明的內容。

小結

基本上在 Rails 裡使用 Webpack 打包,一開始會很不習慣,特別對新手來說更是痛苦,因為這等於是一次要處理兩個生態圈,所以頭暈是正常的,不過我應該慢慢的就會習慣它了(吧)。

工商服務

實體課程:Ruby on Rails 實戰課程
線上課程:五倍學院線上課程