鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起實驗了用 importmap 來安裝 bootstrap,今天來繼續延續 Rails 7 的研究,要試試看用 jsbundling-rails
這個 gem 來處理 JS,這個 gem 可以讓我們選擇四種處理 JS 的方法,第一個是預設的 importmap,第二個是最近很紅的 esbuild,第三個是 rollup,第四個是 webpack,我們今天要實驗的是選擇 webpack 來試試看,看用起來感覺怎麼樣、跟 Rails 6 使用 webpacker 的感覺有沒有差別,夠夠~
$ rails new rails7_webpack -j webpack
在 Rails 7 中,如果沒有帶 -j
,預設就是使用 importmap
package.json
: 使用 webpacker 打包時期,熟悉的 package.json
出現了!
Procfile.dev
: 紀錄啟動 Rails 和 webpack 的啟動程序
web: bin/rails server -p 3000
js: yarn build --watch
webpack.config.js
: 之前 webpacker 時期是放在 config/webpack/xxx.js
,現在被搬出來外面了
Gemfile
: 跟昨天的 importmap 實驗相比,這邊就改安裝 jsbundling-rails
gem,其他的 gem 就跟昨天一樣
...
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.4"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Use Sass to process CSS
# gem "sassc-rails"
...
$ rails g controller home index
把新的頁面設為首頁 config/routes.rb
Rails.application.routes.draw do
root 'home#index'
end
檢查 app/views/layouts/application.html.erb
引用 js 的方法
<!DOCTYPE html>
<html>
<head>
<title>Rails7Webpack</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
</head>
<body>
<%= yield %>
</body>
</html>
我一開始還以為是錯的,我一度把 javascript_include_tag
改回 javascript_pack_tag
,一改就報錯,才發現原來 javascript_pack_tag
是 webpacker 的 helper method,所以這邊預設的 javascript_include_tag
是對的,不需要特別去動它
在 app/views/home/index.html.erb
新增一個極簡按鈕,並給他一個 id
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<%= button_tag 'Click Me', id: 'btn-click' %>
有了上次 webpacker 實驗的教訓之後,這次已經知道不能直接在 view 裡面呼叫 js 的 function 了,所以我們就給它一個 id,後面再由 js 來操作
新增 app/javascript/my.js
,針對 btn 新增一個 click 的 listener
// app/javascript/my.js
console.log('my.js')
function clicked() {
alert('Clicked')
}
var btn = document.getElementById('btn-click')
btn.addEventListener("click", function() {
clicked()
});
在 app/javascript/application.js
引用 my.js
// app/javascript/application.js
import './my.js'
啟動 $ ./bin/dev
,並打開 127.0.0.1:3000
,確認一下按鈕有沒有作用(./bin/dev
這東東是由 foreman
這個 gem 幫忙生成的,在創立專案的時候就自動安裝了,而且是沒有記錄在 Gemfile.lock
的)
因為要用 webpack 打包 js,所以如果只有啟動 $ rails s
,那前端的 js 並不會被 webpack 打包到了
之前使用 webpacker 的時候,只需要使用 $ rails s
就可以了,這就是整合的威力 XD
成功~
經過了昨天使用 importmap 安裝 bootstrap 的慘痛經驗,今天再次體驗看看用 webpack 來使用 bootstrap
Gemfile
安裝 bootstrap
和 sassc-rails
gem,$ bundle install
# Gemfile
...
# Use Sass to process CSS
gem "sassc-rails"
# Use bootstrap
gem 'bootstrap'
重新命名 app/assets/stylesheets/application.css
為 app/assets/stylesheets/application.scss
,並引用 bootstrap
css
@import 'bootstrap';
安裝 bootstrap js 部分,$ yarn add bootstrap @popperjs/core
在 app/javascript/application.js
引用 bootstrap 和 popperjs
// app/javascript/application.js
...
import './my.js'
import '@popperjs/core'
import 'bootstrap'
在 app/views/home/index.html.erb
使用 bootstrap 的 navbar, popover, tooltip, dropdown btn, modal
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" aria-label="Ninth navbar example">
<div class="container-xl">
<a class="navbar-brand" href="#">Container XL</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample07XL" aria-controls="navbarsExample07XL" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExample07XL">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
</ul>
<form role="search">
<input class="form-control" type="search" placeholder="Search" aria-label="Search">
</form>
</div>
</div>
</nav>
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<%= content_tag(:p, 0, id: 'count') %>
<%= button_tag 'Click Me', class: 'btn btn-primary', id: 'btn-click' %>
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown button
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">Dropdown header</h6></li>
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Separated link</a></li>
</ul>
</div>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
Tooltip on top
</button>
<!-- popover -->
<button type="button" class="btn btn-secondary" data-bs-container="body" data-bs-toggle="popover" data-bs-placement="top" data-bs-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." aria-describedby="popover307994">
Popover on top
</button>
啟動 $ ./bin/dev
,會發現只有 dropdown 和 modal 是正常的,tooltip 和 popver 不能正常運作
改一下 app/javascript/application.js
,從 import 'bootstrap'
變成 import * as bootstrap from 'bootstrap'
,再從 bootstrap 的 Enable Tooltips 和 Enable Popover,複製來貼上,變成以下這樣
import './my.js'
import '@popperjs/core'
import * as bootstrap from 'bootstrap'
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
再次啟動 $ ./bin/dev
,並打開 127.0.0.1:3000
,驗證一下元件是不是都可以運作
成功~
結果看樣子,應該是我昨天錯怪 importmap 了…,是我少加了 enable tooltips 和 popover 的程式碼了,我有把今天 enable tooltip 和 popover 的方法貼過去 importmap 專案,確定是可以正常運作的,抱歉了 importmap QQ
不過這兩天用起來,體感上使用 webpack 還是比 importmap 好,雖然 webpack 用起來還是有些延遲,有時候我改完 js 並存檔之後,我還在等 webpack 打包,打包完我才重新整理頁面,有時候我乾脆不等,就直接重開 $ ./bin/dev
,聽說 esbuild 會比 webpack 更快,我期待明天的實驗,就來玩玩看 esbuild,我們明天見~