如何在 Rails 使用 Webpacker(中)
在前一篇文章介紹了如何在 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
來自動幫我們載入模組,就不用自己手動 import
或 require
了。
參考資料: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
起手式都差不多,都是用 yarn
或 npm
把套件拉回來:
$ 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 叫做 theCutePicker
的 input
元素:
// 檔案: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
裡了:
這個 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 進來,然後再回到瀏覽器看看:
搞定,瞧瞧這精美的 Date Picker!
等等...好像哪裡怪怪的,為什麼剛剛是在一個 JavaScript 檔案裡面 import 一個 CSS 檔案?可以這樣嗎?
這個嘛,其實對 Webpack 來說,所有的東西都是 JavaScript,而這也是下一篇文章要再說明的內容。
小結
基本上在 Rails 裡使用 Webpack 打包,一開始會很不習慣,特別對新手來說更是痛苦,因為這等於是一次要處理兩個生態圈,所以頭暈是正常的,不過我應該慢慢的就會習慣它了(吧)。
工商服務
實體課程:Ruby on Rails 實戰課程
線上課程:五倍學院線上課程