🔁 ทวนก่อนเริ่ม — คำจากบทก่อน ๆ ที่บทนี้ใช้
- Tailwind · utility class (บท 72) — คลาสสั้น ๆ อย่าง
p-4text-3xlที่แต่ละตัวคือ CSS หนึ่งบรรทัด · Tailwind อ่านไฟล์ของเราแล้วสร้างไฟล์ CSS เฉพาะคลาสที่เจอ @themeแทนtailwind.config.js(บท 72 หัวข้อ 4) — Tailwind v4 ตั้งค่าในไฟล์ CSS · ไฟล์ config แบบ JavaScript เป็นของ v3- semantic token (บท 81) — ตั้งชื่อสีตามหน้าที่ (
primary) แทนชื่อสีตรง ๆ (blue-600) · เปลี่ยนค่าที่เดียว ทั้งแอปเปลี่ยนตาม app/assets/builds/tailwind.css·assets:precompile·assets:clobber(บท 127) — ไฟล์ CSS ที่ Tailwind สร้างให้ · คำสั่งเตรียมไฟล์แบบขึ้นเครื่องจริง · คำสั่งล้างของที่เตรียมไว้prompts/00-context.md(บท 121) — แฟ้มคู่มือพนักงานใหม่ที่แนบให้ AI ทุกครั้ง
🧰 ตรวจด้วย Ruby 4.0.9 · Rails 8.1.4 · tailwindcss-rails 4.7.0 (Tailwind 4.3.3) · daisyUI 5.7.50 · Chrome (สไลด์และ repo ของอาจารย์ใช้ Ruby 4.0.5–4.0.6 · Rails 8.1.3 — เรื่องในบทนี้ไม่ต่างกัน)
🗂 โค้ดเริ่มจาก: แอปหลังจบ Lab บท 126 (ตามไม่ทันดูบท 126) · บทนี้ทดลองแล้วทิ้ง —
จบบทให้ git restore . + bin/rails assets:clobber + bin/rails tailwindcss:build (บอกอีกทีท้ายภารกิจ)
ผลทุกก้อนรันจริงแล้วก็อปมา · เวลา (ms) · เวลาบนหัวบรรทัดของ bin/dev · เลข PID · คำว่า "1 day ago" ของคุณจะต่างจากของเรา
Tailwind ในแอป Rails อยู่ตรงไหน — เปิดไฟล์ตั้งค่าของจริง
ทวนจากบท 72 ครึ่งย่อหน้า: เราเขียนคลาสอย่าง font-bold ใน view · Tailwind อ่านไฟล์ทุกไฟล์ในโปรเจกต์เป็นตัวหนังสือ
แล้วเขียน CSS เฉพาะคลาสที่เจอลงไฟล์เดียว · ไฟล์ CSS จึงเล็ก เพราะไม่มีคลาสที่ไม่มีใครใช้
ใน Next.js เราได้ Tailwind ผ่าน PostCSS และ npm · ใน Rails ใช้ gem tailwindcss-rails ซึ่งพกโปรแกรม Tailwind มาเป็นไฟล์เดียว ไม่ต้องมี Node ไม่ต้องมี npm ·
ทางเดินของมันมีแค่นี้:
อ่านแผนภาพ: app/views/**/*.erb = "ไฟล์ .erb ทุกไฟล์ ในทุกโฟลเดอร์ย่อยของ app/views" (** = ลงไปกี่ชั้นก็ได้) · ลูกศรขวาคือทางที่ CSS เดินต่อ ·
ลูกศรขึ้นจากด้านล่างคือไฟล์ตั้งค่าที่บอกว่าให้ Tailwind ทำอะไร
ไฟล์ตั้งค่า (ใบสั่งงาน) ของ Tailwind คือ app/assets/tailwind/application.css · เปิดดูของจริงในแอป (สถานะหลังบท 126):
/* app/assets/tailwind/application.css */
@import "tailwindcss";
@source not "./daisyui{,*}.mjs";
@plugin "./daisyui.mjs";
@theme {
--font-sans: "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif;
}
อ่านทีละบรรทัด:
@import "tailwindcss";= "เอา Tailwind ทั้งชุดมาใช้" · บรรทัดเดียวกับในglobals.cssของ Next.js (บท 72) เป๊ะ@source not "./daisyui{,*}.mjs";= "อย่าอ่านไฟล์daisyui.mjsกับdaisyui-ตัวอะไรก็ได้.mjs" ({,*}แปลว่า "ไม่มีอะไรต่อ หรือมีอะไรต่อก็ได้") · สองไฟล์นี้เป็นโค้ดของ daisyUI ที่มีชื่อคลาสทุกตัวของมันอยู่ข้างใน · ผมลองลบบรรทัดนี้แล้ว build ไฟล์ CSS โตจาก 34,146 เป็น 380,038 byte (มี.modalที่เราไม่เคยใช้โผล่มาด้วย)@plugin "./daisyui.mjs";= "เสียบส่วนเสริม daisyUI จากไฟล์ข้าง ๆ นี้" · เดี๋ยวดูละเอียดในหัวข้อ daisyUI@theme { … }= ที่ตั้งค่าของร้านเรา · ข้างในเป็นตัวแปร CSS (ชื่อขึ้นต้นด้วย--ใช้เก็บค่าไว้ใช้ซ้ำ · เคยเห็นในบท 81) · ตอนนี้มีค่าเดียวคือ--font-sans(ฟอนต์หลัก) — ลองใช้ฟอนต์"Noto Sans Thai"ก่อน · เครื่องไหนไม่มีฟอนต์นี้ก็ข้ามไปใช้ตัวถัดไปในรายการ
แล้วสั่ง build เองหนึ่งรอบ (เปิด terminal ที่โฟลเดอร์โปรเจกต์):
$ bin/rails tailwindcss:build
≈ tailwindcss v4.3.3
/*! 🌼 daisyUI 5.7.50 */
Done in 81ms
บรรทัดแรกบอกรุ่นของ Tailwind · บรรทัดดอกไม้บอกว่า daisyUI ถูกเสียบเข้ามาแล้ว · Done in 81ms = เสร็จใน 0.08 วินาที (ของคุณจะต่างนิดหน่อย)
ผลลัพธ์ไปอยู่ที่ app/assets/builds/tailwind.css ซึ่งอยู่ใน .gitignore (ไฟล์รายชื่อของที่ git ไม่ต้องเก็บ) ·
Tailwind ข้ามไฟล์ในรายชื่อนั้นด้วย จึงไม่อ่านผลงานของตัวเองวนไปมา
Tailwind คือฝ่ายจัดซื้อของร้าน · ก่อนเปิดร้าน เขาเดินอ่านรายการเมนูทุกแผ่นที่เขียนไว้บนกระดาษ (ไฟล์ view) แล้วซื้อวัตถุดิบเฉพาะที่เห็นชื่อ (เขียน CSS เฉพาะคลาสที่เจอ) ·
ของที่ไม่มีเมนูไหนเขียนถึง ไม่ซื้อ ตู้เย็นจึงไม่รก
application.css คือใบสั่งงานที่แปะไว้บนโต๊ะฝ่ายจัดซื้อ: "ใช้วัตถุดิบมาตรฐานชุดนี้ (@import) · ห้ามอ่านคู่มือของซัพพลายเออร์ (@source not) · รับชุดจานชามจากเจ้านี้ (@plugin) · สีประจำร้านคือ… (@theme)"
ที่ไม่เหมือนกัน: ฝ่ายจัดซื้อจริงอ่านแล้วเข้าใจความหมาย เห็นคำว่า "ต้มยำ" ก็รู้ว่าต้องซื้อข่าตะไคร้ · Tailwind ไม่เข้าใจอะไรเลย มันแค่หาคำที่หน้าตาตรงกับชื่อคลาสที่มันรู้จัก · และมันอ่านทุกไฟล์ในโปรเจกต์ ไม่ใช่แค่ view — ไฟล์จดโน้ตหรือไฟล์ HTML เก่าที่วางทิ้งไว้ในโปรเจกต์ ถ้ามีชื่อคลาสอยู่ข้างใน มันก็ซื้อของให้ด้วย
- tailwindcss-rails ·
tailwindcss:build - gem ที่พาโปรแกรม Tailwind มาใช้ใน Rails โดยไม่ต้องมี Node · คำสั่ง build = อ่านไฟล์แล้วสร้าง
app/assets/builds/tailwind.cssหนึ่งรอบ · พิมพ์บอก AI ได้ว่า "this app uses tailwindcss-rails (standalone CLI, no Node) — the entry file is app/assets/tailwind/application.css" - source ·
@source - ไฟล์ที่ Tailwind อ่านเพื่อหาชื่อคลาส · ค่าเริ่มต้นคือทุกไฟล์ในโปรเจกต์ ยกเว้นที่อยู่ใน
.gitignore·@source not "…"= สั่งให้ข้ามไฟล์นี้ · ถาม senior ได้ว่า "ไฟล์ CSS ใหญ่ผิดปกติ มีไฟล์ไหนที่ Tailwind ไม่ควรสแกนไหม"
v3 กับ v4 — ไม่มี tailwind.config.js แล้ว และทำไมต้องบอก AI
บท 72 เทียบไว้แล้วว่า Tailwind v3 ตั้งค่าในไฟล์ JavaScript ชื่อ tailwind.config.js · v4 ย้ายทุกอย่างมาอยู่ในไฟล์ CSS · ในแอป Rails ของเราหน้าตาเป็นแบบนี้
(สมมติอยากได้สีประจำร้านชื่อ brand):
v3 — ของเก่าที่อินเทอร์เน็ตเต็มไปหมด
// tailwind.config.js
module.exports = {
content: ["./app/views/**/*.erb"],
theme: { extend: { colors: {
brand: "#ff3d7f"
} } },
}
v4 — ของจริงในแอปเรา
/* app/assets/tailwind/application.css */
@theme {
--font-sans: "Noto Sans Thai", …;
--color-brand: #ff3d7f;
}
/* แล้วใช้ class="text-brand" ได้เลย */
ฝั่งซ้ายเป็น JavaScript: module.exports = { … } = "ส่งก้อนตั้งค่านี้ออกไปให้โปรแกรมอื่นใช้" · content = รายชื่อไฟล์ที่ให้อ่าน · extend.colors = "เพิ่มสีนี้ต่อจากสีมาตรฐาน" ·
ไม่ต้องจำ แค่จำหน้าตาไว้ — เห็นแบบนี้ในคำตอบของ AI หรือในเว็บ = ของ v3
ฝั่ง v4 ไม่ต้องบอก content (ไฟล์ไหนให้อ่าน) เพราะมันอ่านทุกไฟล์เอง · ชื่อตัวแปรมีแบบแผน: --color- นำหน้า = สี · ได้คลาส text-brand bg-brand border-brand มาทันที
ลองจริง: เพิ่มบรรทัด --color-brand: #ff3d7f; ใน @theme ตามฝั่งขวา แล้ว build
$ bin/rails tailwindcss:build
$ grep -c -- '--color-brand' app/assets/builds/tailwind.css
ยังไม่มี view ไหนใช้คลาส text-brand เลย · grep -c นับว่ามีกี่บรรทัดที่เจอคำนี้ — ไฟล์ CSS ที่ได้ถูกบีบเหลือบรรทัดเดียว ผลจึงมีแค่ 1 = เจอ · 0 = ไม่เจอ (ใช้แบบนี้ทั้งบท) ·
-- ก่อนคำค้น = บอก grep ว่า "คำที่ตามมาไม่ใช่ตัวเลือกของ grep" เพราะมันขึ้นต้นด้วยขีดสองตัว
ตัวแปร --color-brand จะอยู่ในไฟล์ CSS ที่ได้ไหม?
≈ tailwindcss v4.3.3
/*! 🌼 daisyUI 5.7.50 */
Done in 78ms
0
ไม่อยู่ · Tailwind v4 ใส่ตัวแปรของ @theme ลงไฟล์เฉพาะตัวที่มีคนใช้ — กฎเดียวกับคลาส · ฝ่ายจัดซื้อไม่ซื้อสีที่ไม่มีเมนูไหนสั่ง
ทีนี้ใส่ text-brand ให้หัวข้อหน้า app/views/posts/index.html.erb
(ทวนบท 125–126: บรรทัดขึ้นต้น - = ของเดิมที่ลบ · + = ของใหม่ที่ใส่แทน · ตอนพิมพ์จริงไม่ต้องพิมพ์ - +):
- <h1 class="font-bold text-3xl sm:text-4xl">บทความทั้งหมด</h1>
+ <h1 class="font-bold text-3xl sm:text-4xl text-brand">บทความทั้งหมด</h1>
$ bin/rails tailwindcss:build
$ grep -o -- '--color-brand:[^;}]*' app/assets/builds/tailwind.css
--color-brand:#ff3d7f
$ grep -o '\.text-brand{[^}]*}' app/assets/builds/tailwind.css
.text-brand{color:var(--color-brand)}
เพราะไฟล์ถูกบีบเป็นบรรทัดยาวบรรทัดเดียว (minify) จึงใช้ grep -o = "พิมพ์เฉพาะส่วนที่ตรง" · [^;}]* = "ตัวอะไรก็ได้ที่ไม่ใช่ ; หรือ }" (อ่านไปจนจบค่า) ·
ได้ทั้งตัวแปร และคลาส .text-brand ที่ชี้ไปหาตัวแปรนั้น · เปิดหน้า /posts หัวข้อเป็นสีชมพู
(ตั้งแต่ตรงนี้ ผมตัดข้อความ 3 บรรทัดของ build — รุ่น Tailwind · ดอกไม้ daisyUI · Done in … — ออก เหลือแค่ผลของ grep)
แล้วถ้าทำตามเว็บเก่า ๆ สร้าง tailwind.config.js ล่ะ? ผมสร้างไฟล์นี้ที่โฟลเดอร์บนสุดของโปรเจกต์ ตั้งสีชื่อ mint แล้วใส่ text-mint ใน <h1> เดิม:
// tailwind.config.js — ไฟล์ทดลอง ลบทิ้งหลังลอง
module.exports = {
content: ["./app/views/**/*.html.erb"],
theme: { extend: { colors: { mint: "#3eb489" } } },
}
$ bin/rails tailwindcss:build
≈ tailwindcss v4.3.3
/*! 🌼 daisyUI 5.7.50 */
Done in 88ms
$ grep -c 'text-mint' app/assets/builds/tailwind.css
0
ไม่มี error สักบรรทัด · build ผ่าน · แต่สีไม่มา · v4 ไม่เปิดไฟล์นี้เลย มันวางอยู่เฉย ๆ เหมือนกระดาษที่ไม่มีใครอ่าน
นี่คือเหตุผลที่ prompts/00-context.md ของแอปนี้มีบรรทัดนี้อยู่แล้ว (เปิดดูได้ — บท 121 พาอ่านไปแล้ว):
- tailwindcss-rails (Tailwind v4) + daisyUI 5 ผ่าน @plugin ในไฟล์ CSS
ห้ามสร้าง tailwind.config.js — Tailwind v4 ไม่อ่านไฟล์นั้น
AI ฝึกจากเนื้อหาในอินเทอร์เน็ต · v3 อยู่มาหลายปี ตัวอย่างเยอะกว่า v4 มาก · ถ้าไม่บอก มันมีโอกาสสูงที่จะตอบแบบ v3 ("สร้าง tailwind.config.js แล้วใส่ content") ·
เราทำตาม → ไม่มี error → นั่งงงว่าทำไมสีไม่มา · บรรทัดเดียวใน 00-context.md ตัดปัญหานี้ทิ้งทั้งก้อน
ชื่อคลาสบางตัวก็ขยับหนึ่งขั้นใน v4 เช่น เงาบาง ๆ ที่ v3 เรียก shadow-sm ตอนนี้ชื่อ shadow-xs ·
โค้ดจาก AI ที่ใช้ชื่อแบบ v3 จึงไม่พัง แต่หน้าตาต่างจากที่ตั้งใจนิดหน่อย (เงาหนากว่าหนึ่งขั้น) · ตารางชื่อที่เปลี่ยนอยู่ใน upgrade guide ท้ายบท
ร้านเราเคยมีสมุดสั่งงานฝ่ายจัดซื้อแยกเป็นเล่ม (v3 · tailwind.config.js) · พอเปลี่ยนระบบ ร้านย้ายคำสั่งทั้งหมดไปเขียนในใบสั่งงานที่แปะบนโต๊ะ (v4 · @theme ใน CSS) แล้วเลิกเปิดสมุดเล่มเก่า ·
พนักงานใหม่ (AI) ที่เคยทำร้านอื่นมา จะหยิบสมุดเล่มเก่ามาเขียนคำสั่งเพิ่ม — เขียนสวยแค่ไหนก็ไม่มีใครอ่าน
ที่ไม่เหมือนกัน: ร้านจริงอาจมีคนเห็นสมุดเล่มเก่าแล้วเตือน · Tailwind v4 เงียบสนิท ไม่เตือนว่ามีไฟล์ที่มันไม่อ่าน ·
เราจึงต้องเป็นคนเตือนพนักงานใหม่เองตั้งแต่วันแรก (บรรทัดใน 00-context.md ข้างบน)
- theme variable (
--color-*·--font-*) - ตัวแปร CSS ใน
@themeที่ชื่อขึ้นต้นตามแบบแผน แล้ว Tailwind สร้างคลาสให้ (--color-brand→text-brand·bg-brand) · ใส่ลงไฟล์เฉพาะตัวที่ถูกใช้ · พิมพ์บอก AI ได้ว่า "add a brand color as a Tailwind v4 theme variable in @theme, not in tailwind.config.js" - Tailwind v3 vs v4
- v3 = ตั้งค่าใน
tailwind.config.js+ ต้องบอกcontent· v4 = ตั้งค่าใน CSS + หาไฟล์เอง · ไวยากรณ์ v3 ในโปรเจกต์ v4 มักไม่ error แค่ไม่มีผล · ถาม senior ได้ว่า "ตัวอย่างที่ผมเจอเป็น v3 ใช่ไหม ต้องแปลงอะไรบ้างก่อนใช้กับ v4"
daisyUI 5 — ชุดจานชามสำเร็จรูปที่วางบน Tailwind
Tailwind ให้อิฐทีละก้อน · ปุ่มหนึ่งปุ่มต้องเรียงคลาสยาว ๆ เอง · daisyUI คือส่วนเสริม (plugin) ที่ตั้งชื่อให้ชุดคลาสที่ใช้บ่อย
เช่น btn = หน้าตาปุ่มทั้งชุด · btn-primary = ปุ่มสีหลักของร้าน · มีทั้ง card · navbar · badge · alert ฯลฯ (สไลด์อ้างว่าราว 60 ตัว) และใช้ CSS ล้วน ไม่มี JavaScript
Tailwind เปล่า ๆ
<button class="px-4 py-2 rounded-lg bg-blue-600
text-white font-medium hover:bg-blue-700
focus:ring-2 focus:ring-blue-400
disabled:opacity-50 transition">
บันทึก
</button>
daisyUI
<button class="btn btn-primary">บันทึก</button>
<%# ผสมกับคลาส Tailwind ได้ตามสบาย %>
<button class="btn btn-primary w-full mt-4">
บันทึก
</button>
ฝั่งซ้าย: px-4 py-2 = ระยะในซ้ายขวา/บนล่าง · rounded-lg = มุมมน · hover: focus: disabled: = ใช้คลาสนี้เฉพาะตอนเมาส์ชี้/ตอนถูกเลือก/ตอนกดไม่ได้ (บท 72) ·
ring-2 = เส้นขอบเรืองรอบปุ่ม · opacity-50 = จางลงครึ่งหนึ่ง · transition = เปลี่ยนสีแบบนุ่ม ๆ ไม่กระตุก
ฝั่งขวาได้ของพวกนี้ครบในสองคำ · w-full mt-4 ต่อท้าย = คลาส Tailwind ธรรมดา ใช้ปนกันได้ · <%# %> = คอมเมนต์ของ ERB (บท 122)
daisyUI ในแอปนี้มาเป็นไฟล์ ไม่ได้ลงด้วย npm · ls app/assets/tailwind แล้วเจอ:
$ ls app/assets/tailwind
application.css
daisyui-theme.mjs
daisyui.mjs
$ ls tailwind.config.js package.json node_modules
ls: node_modules: No such file or directory
ls: package.json: No such file or directory
ls: tailwind.config.js: No such file or directory
daisyui.mjs คือตัว daisyUI ทั้งก้อนที่ก็อปมาวางในโปรเจกต์ (.mjs = ไฟล์ JavaScript ชนิดหนึ่ง · Tailwind เรียกใช้ตอน build ไม่ได้ส่งไปเบราว์เซอร์) ·
daisyui-theme.mjs เป็นตัวช่วยสร้างธีมของเราเอง ซึ่งแอปนี้ยังไม่ได้เรียกใช้ ·
การก็อป library มาเก็บในโปรเจกต์แบบนี้เรียกว่า vendor · ไม่มี package.json ไม่มี node_modules ตรงกับที่บท 127 เล่าว่าแอป Rails 8 ไม่ต้องมี Node
สไลด์หน้า 29–30 เขียน @plugin "daisyui"; (แบบโปรเจกต์ที่ลงด้วย npm) · พิมพ์ตามในแอปนี้แล้ว build พังทันที:
≈ tailwindcss v4.3.3
Error:
┌
│ Error: Can't resolve 'daisyui' in '…/app/assets/tailwind'
└
bin/rails aborted!
แอปเราต้องเขียน @plugin "./daisyui.mjs"; (มี ./ และ .mjs) · ส่วน … คือ path โฟลเดอร์ของคุณ
daisyUI ทำตามกฎฝ่ายจัดซื้อเหมือนกัน · ก่อนใส่ปุ่มในหน้าไหนเลย grep -c '\.btn-primary{' ในไฟล์ CSS ได้ 0 · พอเพิ่ม <button class="btn btn-primary"> ลงใน view แล้ว build ได้ 1 ·
ใช้ชิ้นไหน ได้ CSS ชิ้นนั้น ไม่ได้ยัด 60 ชิ้นมาทั้งหมด
(คำค้น \.btn-primary{ = "จุด ตามด้วยชื่อคลาส ตามด้วย {" ซึ่งคือหน้าตาของกฎ CSS ในไฟล์ · \. แปลว่าจุดจริง ๆ เพราะจุดเปล่า ๆ ใน grep แปลว่า "ตัวอะไรก็ได้")
Tailwind เปล่า ๆ เหมือนร้านที่ซื้อดิน สี เตาเผา มาปั้นจานเองทุกใบ — ได้ตามใจทุกอย่าง แต่จานแต่ละใบอาจไม่เข้ากัน ·
daisyUI คือชุดจานชามสำเร็จรูปจากโรงงาน สั่งว่า "จานใหญ่ 1 · ถ้วยน้ำจิ้ม 2" (btn · card · badge) ได้ของที่ลวดลายเข้าชุดกันทันที ·
อยากเติมอะไรก็เอาสีมาแต้มเพิ่มบนจานสำเร็จได้ (w-full mt-4)
ที่ไม่เหมือนกัน: จานโรงงานจริงเป็นของคนละชนิดกับจานปั้นเอง · daisyUI ปั้นด้วยดินชุดเดียวกับ Tailwind — ข้างใน btn ก็คือค่า CSS ชุดเดียวกับที่เราเขียนเองได้ แค่มีชื่อเรียก ·
และโรงงานนี้ไม่ส่งของล่วงหน้า ใบไหนไม่มีใครสั่งในกระดาษเมนู ก็ไม่ผลิต
- component class (daisyUI)
- ชื่อคลาสหนึ่งคำที่แทนชุดคลาส Tailwind ทั้งชุด (
btn·card·badge·navbar) · ผสมกับ utility class ได้ · พิมพ์บอก AI ได้ว่า "use daisyUI 5 component classes (btn, card, badge) instead of long Tailwind class lists" - vendor · vendored library ·
@plugin - vendor = ก็อปไฟล์ของ library มาเก็บในโปรเจกต์เอง แทนการลงผ่าน npm ·
@plugin "./daisyui.mjs"= เสียบไฟล์นั้นเข้า Tailwind · ถาม senior ได้ว่า "daisyUI ของเรา vendored ไว้ ถ้าจะอัปเกรดต้องเปลี่ยนไฟล์ยังไง"
สีตามหน้าที่ และ data-theme — เปลี่ยนธีมทั้งร้านด้วยป้ายเดียว
ทวนบท 81: bg-blue-600 = "น้ำเงินเบอร์ 600" ตายตัว · btn-primary = "สีหลักของร้าน" ซึ่งจะเป็นสีอะไรก็ขึ้นกับธีม ·
daisyUI ตั้งชื่อสีตามหน้าที่ไว้ชุดหนึ่ง เช่น primary (สีหลัก) · secondary · accent · base-100 (สีพื้นหลังหน้า) · base-content (สีตัวหนังสือบนพื้น)
daisyUI มีธีมสำเร็จ 35 ชุด (นับจากไฟล์ daisyui.mjs รุ่น 5.7.50 จริง) · สลับธีมด้วยการใส่ attribute data-theme ที่แท็ก <html> บนสุดของหน้า:
<html data-theme="dark">
<html data-theme="cupcake">
ผมวางปุ่มสองปุ่มไว้ใน app/views/posts/index.html.erb — ปุ่ม daisyUI กับปุ่ม Tailwind เปล่า — แล้วเปิดด้วย Chrome จริง · เปลี่ยน data-theme ทีละค่าแล้ววัดสีพื้นที่เบราว์เซอร์ใช้จริง
(oklch(…) คือวิธีเขียนสีอีกแบบ เหมือน #ff3d7f · ไม่ต้องอ่านออก ดูแค่ว่าเปลี่ยนหรือไม่เปลี่ยน · ตัวเลขแรกคือความสว่าง 0–1):
<div class="my-4 flex gap-2">
<button class="btn btn-primary">บันทึก</button>
<button class="px-4 py-2 rounded-lg bg-blue-600 text-white">บันทึก</button>
</div>
data-theme บน <html> | พื้นปุ่ม btn-primary | พื้นปุ่ม bg-blue-600 | พื้นหลังหน้า |
|---|---|---|---|
| (ไม่ใส่ · เครื่องตั้งโหมดสว่าง) | oklch(0.45 0.24 277.023) | oklch(0.546 0.245 262.881) | oklch(1 0 0) ขาว |
"light" | oklch(0.45 0.24 277.023) | oklch(0.546 0.245 262.881) | oklch(1 0 0) ขาว |
"dark" | oklch(0.58 0.233 277.117) | oklch(0.546 0.245 262.881) | oklch(0.2533 0.016 252.42) เกือบดำ |
ปุ่ม btn-primary เปลี่ยนสีตามธีม (สว่างขึ้นให้เด่นบนพื้นมืด) · ปุ่ม bg-blue-600 สีเดิมทุกแถว เพราะเราสั่งเลขสีตายตัวไว้ ·
ตอนเป็น dark หน้าจอจริงเห็นอีกอย่าง: คำอธิบายบทความที่เป็น text-gray-700 (เทาเข้มตายตัว) แทบจมหายไปในพื้นมืด — นี่คือเหตุผลที่ Lab บท 129 ย้ายหน้าไปใช้สีตามหน้าที่
daisyUI มี 35 ธีม แต่ไม่ได้เปิดใช้ทุกธีม · ไฟล์ application.css ของเราเขียนแค่ @plugin "./daisyui.mjs"; ไม่ได้บอกว่าเอาธีมไหน
ถ้าใส่ <html data-theme="synthwave"> (ธีมโทนม่วงชมพูนีออน) ปุ่ม btn-primary และพื้นหลังหน้าจะเป็นสีอะไร?
วัดใน Chrome ได้ปุ่ม oklch(0.45 0.24 277.023) · พื้นหลัง oklch(1 0 0) = หน้าตาเหมือนธีม light ทุกอย่าง · ไม่มี error · ไม่มีอะไรเตือน
เพราะค่าเริ่มต้นของ daisyUI 5 เปิดแค่ light กับ dark · ชื่อธีมที่ไม่ได้เปิดไม่มี CSS ให้ใช้ · หน้าเลยตกไปใช้ธีมหลัก (light) · ต้องเปิดเองในบล็อก @plugin:
@plugin "./daisyui.mjs" {
themes: light --default, dark --prefersdark, synthwave;
}
$ bin/rails tailwindcss:build
$ grep -o '\[data-theme=[a-z]*\]' app/assets/builds/tailwind.css | sort | uniq -c
1 [data-theme=dark]
2 [data-theme=light]
1 [data-theme=synthwave]
คำสั่งยาวแปลว่า "ดึงทุกคำที่หน้าตาเป็น [data-theme=ชื่อ] · เรียง · นับว่าแต่ละชื่อมีกี่ครั้ง" · ก่อนเปิด มีแค่ dark กับ light · หลังเปิด synthwave โผล่มา ·
วัดใน Chrome ใหม่: ปุ่ม oklch(0.71 0.202 349.761) (ชมพู) · พื้นหลัง oklch(0.15 0.09 281.288) (ม่วงเข้ม)
อ่านบล็อก themes: ทีละส่วน (ไวยากรณ์นี้ตรวจกับ daisyUI 5.7.50 ที่ติดตั้งจริงแล้ว ใช้ได้ตามสไลด์หน้า 30):
themes:ตามด้วยรายชื่อธีมคั่นด้วย,ปิดด้วย;= เปิดเฉพาะธีมเหล่านี้light --default= ธีมที่ใช้เมื่อไม่ได้ใส่data-themedark --prefersdark= ถ้าเครื่องผู้ใช้ตั้งโหมดมืดไว้ และไม่ได้ใส่data-themeให้ใช้ธีมนี้ · ผมตั้ง Chrome ให้ "เครื่องเป็นโหมดมืด" แล้วเปิดหน้า: ไม่ต้องใส่อะไร ปุ่มได้oklch(0.58 0.233 277.117)= สีของ dark- เขียนแค่
light --default, dark --prefersdark;ได้ไฟล์ CSS เหมือนไม่เขียนเลยทุกไบต์ (ผมเทียบด้วยcmpแล้ว) — เพราะนั่นคือค่าเริ่มต้นอยู่แล้ว · เขียนไว้ให้คนอ่านรู้ว่าเปิดธีมอะไร
อยากลองเปลี่ยนธีมเองโดยยังไม่แก้ไฟล์: เปิดหน้า /posts ใน Chrome → กด F12 (หรือ คลิกขวา → Inspect) → แท็บ Console → พิมพ์:
document.documentElement.setAttribute("data-theme", "dark")
document.documentElement = แท็ก <html> ของหน้านี้ · setAttribute("ชื่อ", "ค่า") = ใส่ attribute ให้แท็กนั้น · กด Enter แล้วหน้าเปลี่ยนเป็นโหมดมืดทันที ·
refresh แล้วกลับเป็นเหมือนเดิม เพราะเราแก้แค่ในเบราว์เซอร์ (ปุ่มสลับธีมที่จำค่าได้ทำใน Lab บท 129)
สั่งพนักงานทาสีว่า "ทาน้ำเงินเบอร์ 600" (bg-blue-600) → ไม่ว่าร้านจะจัดธีมอะไร ผนังนั้นก็น้ำเงินเบอร์ 600 ·
สั่งว่า "ทาสีประจำร้าน" (btn-primary) → พนักงานไปดูป้ายธีมหน้าร้าน (data-theme บน <html>) ว่าวันนี้ธีมอะไร แล้วใช้สีของธีมนั้น ·
เปลี่ยนป้ายเดียว ทั้งร้านเปลี่ยนตาม
ที่ไม่เหมือนกัน: ร้านจริงเปลี่ยนธีมต้องทาสีใหม่ใช้เวลาหลายวัน · เบราว์เซอร์เปลี่ยนทันทีเพราะทุกธีมถูกเตรียมไว้ในไฟล์ CSS แล้ว แค่สลับว่าชุดไหนใช้งาน · และป้ายที่เขียนชื่อธีมที่ร้านไม่ได้เตรียมไว้ (synthwave ที่ไม่ได้เปิด) ไม่มีใครฟ้อง — พนักงานใช้ธีมหลักไปเงียบ ๆ
- semantic color (
primary·base-100·base-content) - สีที่ตั้งชื่อตามหน้าที่ ค่าจริงเปลี่ยนตามธีม · ตรงข้ามกับสีตายตัวอย่าง
blue-600gray-700· พิมพ์บอก AI ได้ว่า "use daisyUI semantic colors (bg-base-100, text-base-content, btn-primary) instead of fixed colors like gray-700, so dark mode works" data-theme·--default·--prefersdark- attribute บน
<html>ที่บอก daisyUI ว่าใช้ธีมไหน ·--default= ธีมเมื่อไม่ได้ระบุ ·--prefersdark= ธีมเมื่อเครื่องผู้ใช้ตั้งโหมดมืด · ถาม senior ได้ว่า "เราจะให้ธีมตามเครื่องผู้ใช้ หรือให้ผู้ใช้กดเลือกแล้วจำไว้"
★ กับดักชื่อคลาสที่ต่อ string — ฉบับ ERB
บท 72 หัวข้อ 6 เจอกับดักนี้ใน React แล้ว: className={`text-${color}-600`} สีหาย · ใน ERB หน้าตาต่างกันนิดเดียว แต่เป็นเรื่องเดียวกันทุกอย่าง ·
และเป็นโค้ดที่ AI เขียนให้บ่อยมาก เพราะมันดูฉลาดและสั้น
ลองใน app/views/posts/_post.html.erb (แม่พิมพ์บทความหนึ่งชิ้น จากบท 126) · อยากให้ชื่อหมวด "ของหวาน" เป็นสีชมพู หมวดอื่นน้ำเงิน:
- <span class="font-medium text-blue-700"><%= post.category.name %></span>
+ <% color = post.category.slug == "desserts" ? "pink" : "blue" %>
+ <span class="font-medium text-<%= color %>-700"><%= post.category.name %></span>
บรรทัด <% %> คำนวณในครัวเงียบ ๆ (บท 122): ถ้า slug ของหมวดเป็น "desserts" ได้คำว่า "pink" ไม่งั้น "blue" (เงื่อนไข ? ค่าถ้าจริง : ค่าถ้าเท็จ = if-else แบบบรรทัดเดียว · หน้าตาเดียวกับใน TypeScript) ·
บรรทัดถัดมาเอาคำนั้นไปเสียบกลางชื่อคลาส · รัน bin/dev แล้วเปิด /posts
(bin/dev = เปิด server พร้อมตัวที่สร้าง CSS ใหม่ให้ทุกครั้งที่แก้ไฟล์ · บท 120 เคยใช้ · ละเอียดในหัวข้อถัดไป)
ก่อนแก้ ชื่อหมวดทุกหมวดเป็นสีน้ำเงิน · หลังแก้ "ของหวาน" จะชมพูไหม? แล้วหมวดอื่นยังน้ำเงินไหม?
ไม่มีหมวดไหนมีสีเลย — แม้แต่น้ำเงินที่เมื่อกี้ยังขึ้นอยู่ · HTML ที่ได้ (curl) มีคลาสครบถูกต้อง:
<span class="font-medium text-blue-700">เครื่องดื่ม</span>
<span class="font-medium text-blue-700">อาหารริมทาง</span>
<span class="font-medium text-pink-700">ของหวาน</span>
แต่ในไฟล์ CSS ไม่มีกฎของทั้งคู่:
$ grep -c 'text-pink-700' app/assets/builds/tailwind.css
0
$ grep -c 'text-blue-700' app/assets/builds/tailwind.css
0
Chrome วัดสีชื่อหมวดได้ oklch(0.551 0.027 264.364) ทุกหมวด = สีเดียวกับ <p class="text-sm text-gray-500"> ที่ครอบ <span> นี้อยู่
(span ไม่มีกฎสีของตัวเอง เลยใช้สีของแท็กที่ครอบมันไว้)
น้ำเงินหายเพราะคำว่า text-blue-700 เคยอยู่ในไฟล์นี้ที่เดียวในทั้งโปรเจกต์ · พอเราลบมันไปแทนด้วยการต่อ string ฝ่ายจัดซื้อก็ไม่เห็นคำนี้อีก เลยไม่ซื้อ
เหตุผลเดียวกับเรื่องฝ่ายจัดซื้อตั้งแต่ต้นบท: Tailwind อ่านไฟล์เป็นตัวหนังสือ ไม่ได้รันโค้ดของเรา · สิ่งที่มันเห็นในไฟล์คือ text-<%= color %>-700 ซึ่งไม่ใช่ชื่อคลาสอะไรเลย ·
คำว่า text-pink-700 เพิ่งเกิดตอน Rails เติมแม่แบบ ซึ่งตอนนั้นไฟล์ CSS ถูกสร้างเสร็จไปนานแล้ว
ฝ่ายจัดซื้อซื้อของก่อนร้านเปิด จากเมนูที่เขียนบนกระดาษ · บนกระดาษเขียนว่า "แกงสี ___ (แล้วแต่ลูกค้า)" — ไม่มีชื่อแกงไหนเต็ม ๆ ฝ่ายจัดซื้อเลยไม่ซื้ออะไรเลย ·
ตอนเสิร์ฟ คนจัดจานเติมช่องว่างว่า "แกงเขียวหวาน" — แต่ในตู้ไม่มีพริกแกงเขียว จานออกไปเปล่า ๆ
ท่าที่ถูก = เขียนบนกระดาษให้ครบว่า "แกงเขียวหวาน หรือ แกงเผ็ด" แล้วตอนเสิร์ฟค่อยเลือกหนึ่งอย่าง · ฝ่ายจัดซื้อเห็นทั้งสองชื่อ ซื้อมาครบ
ที่ไม่เหมือนกัน: ร้านจริงขาดของ พนักงานยังวิ่งไปซื้อเพิ่มกลางกะได้ · Tailwind ไม่ได้อยู่ในร้านตอนเสิร์ฟ มันไม่มีทางรู้ว่าตอนเติมแม่แบบเกิดชื่ออะไรขึ้น · และร้านจริงจานเปล่าลูกค้าบ่นทันที · ของเราไม่มี error สักบรรทัด แค่สีหายเงียบ ๆ
ท่าที่ถูก — เขียนชื่อคลาสเต็มทุกตัว แล้วค่อยเลือก:
<% color_class = post.category.slug == "desserts" ? "text-pink-700" : "text-blue-700" %>
<span class="font-medium <%= color_class %>"><%= post.category.name %></span>
$ grep -c 'text-pink-700' app/assets/builds/tailwind.css
1
$ grep -c 'text-blue-700' app/assets/builds/tailwind.css
1
ตอนนี้คำ "text-pink-700" กับ "text-blue-700" อยู่ในไฟล์เป็นตัวหนังสือครบทั้งคำ ฝ่ายจัดซื้อเห็นทั้งคู่ · Chrome วัดได้ชมพู oklch(0.525 0.223 3.958) · น้ำเงิน oklch(0.488 0.243 264.376)
ทำไมบางทีมัน "ดูเหมือนใช้ได้" · ตัวอย่างในสไลด์หน้า 31 ใช้ text-<%= color %>-500 กับ "red"/"green" · ในแอปนี้สีขึ้นจริง —
เพราะไฟล์ที่ scaffold สร้างมามีคำนี้เต็ม ๆ อยู่แล้ว:
$ git grep -n -e "text-red-500" -e "text-green-500" app/views
app/views/categories/_form.html.erb:3: <div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-md mt-3">
app/views/categories/index.html.erb:5: <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%= notice %></p>
…
(git grep = ค้นคำในไฟล์ที่ git เก็บอยู่ · -n = บอกเลขบรรทัด · -e หน้าแต่ละคำ = ค้นหลายคำพร้อมกัน · … = ตัดบรรทัดที่เหลือออก)
โค้ดที่ผิดจึงรอดเพราะโชค · วันหนึ่งมีคนเปลี่ยนเป็น "orange" หรือแก้หน้าตากล่องแจ้งเตือนของ scaffold จนคำนั้นหายไป สีก็หายโดยไม่มีใครแตะบรรทัดนั้นเลย
หลักการที่กว้างกว่า Tailwind: เครื่องมือที่ "อ่านโค้ดโดยไม่รันมัน" (static analysis) มองไม่เห็นของที่โปรแกรมประกอบขึ้นตอนรันเสมอ · ต่อไปเจอเครื่องมือไหนที่ "อ่านไฟล์แล้วเลือกเก็บของ" ให้ถามตัวเองว่ามันเห็นชื่อเต็มไหม
หาจุดต่อ string ในโปรเจกต์ทีเดียวทั้งหมดได้ด้วยคำสั่งนี้ (ลองกับโค้ดที่ผิดข้างบน):
$ git grep -n -E '[a-z]-(<%=|#\{)' app/views
app/views/posts/_post.html.erb:5: <span class="font-medium text-<%= color %>-700"><%= post.category.name %></span>
แปลว่า "หาบรรทัดใน app/views ที่มีตัวอักษร ตามด้วย - แล้วตามด้วย <%= หรือ #{ ทันที" — คือรูปแบบ text-<%= … %> และ "bg-#{…}" (การเสียบค่าใน string ของ Ruby · บท 91) ·
-E = ให้ | ในวงเล็บแปลว่า "หรือ" · \{ = ปีกกาจริง ๆ ·
โค้ดที่สะอาดจะไม่พิมพ์อะไรออกมาเลย
- dynamic class name (ชื่อคลาสที่ต่อ string)
- ชื่อคลาสที่ประกอบจากตัวแปรตอนรัน (
text-<%= color %>-700·"bg-#{status}-100") · Tailwind มองไม่เห็น · แก้ด้วยการเขียนชื่อเต็มทุกตัวแล้วเลือก · พิมพ์บอก AI ได้ว่า "never build Tailwind class names with interpolation — write every full class name literally and pick one" - static analysis
- การวิเคราะห์โค้ดด้วยการอ่านไฟล์ ไม่ได้รัน · เร็วและปลอดภัย แต่เห็นแค่ตัวหนังสือที่เขียนไว้ · Tailwind และเครื่องมือตรวจโค้ดอีกหลายตัวทำงานแบบนี้ · ถาม senior ได้ว่า "เครื่องมือตัวนี้ทำงานแบบ static ใช่ไหม ต้องระวังของที่สร้างตอนรันตรงไหนบ้าง"
bin/dev — เปิดร้านพร้อมฝ่ายจัดซื้อประจำกะ
ทุกครั้งที่เราเพิ่มคลาสใหม่ ไฟล์ CSS ต้องถูกสร้างใหม่ · ถ้าต้องสั่ง tailwindcss:build เองทุกครั้งคงเหนื่อย ·
bin/rails tailwindcss:watch คือตัวที่เฝ้าไฟล์ แก้เมื่อไหร่ build ให้เองทันที · และ bin/dev คือคำสั่งที่เปิด server กับตัวเฝ้าไฟล์พร้อมกันในคำสั่งเดียว ตามรายการในไฟล์ Procfile.dev:
# Procfile.dev
web: bin/rails server
css: bin/rails tailwindcss:watch
หนึ่งบรรทัด = หนึ่งโปรแกรม · ชื่อหน้า : (web · css) เป็นป้ายที่จะโผล่หน้าทุกบรรทัดของ output · รันแล้วได้ (ตัดท้ายมา):
$ bin/dev
10:57:32 web.1 | started with pid 25776
10:57:32 css.1 | started with pid 25777
10:57:32 web.1 | => Booting Puma
…
10:57:33 web.1 | * Listening on http://127.0.0.1:3000
10:57:33 web.1 | * Listening on http://[::1]:3000
10:57:33 web.1 | Use Ctrl-C to stop
10:57:33 css.1 | ≈ tailwindcss v4.3.3
10:57:33 css.1 |
10:57:33 css.1 | /*! 🌼 daisyUI 5.7.50 */
10:57:33 css.1 | Done in 193ms
web.1 = server ของ Rails (ข้อความชุดเดียวกับ bin/rails server) · css.1 = ตัวเฝ้าไฟล์ของ Tailwind ที่ build รอบแรกให้ทันที ·
แก้ view ทีหลังจะเห็นบรรทัด css.1 | Done in … โผล่เพิ่ม · กด Ctrl-C ทีเดียวปิดทั้งคู่ ·
pid = เลขประจำโปรแกรมที่ระบบแจกให้ · เวลาและเลข pid ของคุณจะต่าง
(bin/dev ใช้โปรแกรมชื่อ foreman จัดการหลายโปรแกรมพร้อมกัน · ครั้งแรกถ้าเครื่องไม่มี มันจะขึ้น Installing foreman... แล้วลงให้เอง)
ทีนี้ลองแบบลืม · ปิด bin/dev แล้วเปิดแค่ bin/rails server · เพิ่มคลาส text-orange-600 ให้หัวข้อหน้า /posts:
$ curl -s localhost:3000/posts | grep -o '<h1[^>]*>'
<h1 class="font-bold text-3xl sm:text-4xl text-orange-600">
$ grep -c text-orange-600 app/assets/builds/tailwind.css
0
curl -s localhost:3000/posts = ขอหน้า /posts แบบที่เบราว์เซอร์ได้รับ · | = ส่งผลต่อให้คำสั่งถัดไป · grep -o '<h1[^>]*>' = ดึงเฉพาะแท็กเปิด <h1 …> ·
HTML มีคลาสใหม่แล้ว (Rails อ่าน view ใหม่ทุกครั้งตอน development) · แต่ไฟล์ CSS ยังเป็นฉบับเก่า ไม่มีใครสร้างให้ · หน้าจอจึงไม่ส้ม ·
ปิด server แล้วเปิด bin/dev แทน:
$ grep -c text-orange-600 app/assets/builds/tailwind.css
1
สไลด์หน้า 32 อ้างว่า "เพิ่มคลาสแล้วไม่เห็นผล 90% คือลืมรัน bin/dev" (ตัวเลขนี้ไม่ได้วัด) · แต่อาการตรงกับที่เราเห็นทุกอย่าง · เช็คข้อนี้เป็นอย่างแรกทุกครั้ง
กับดักซ้อน — ตัวเฝ้าไฟล์จำคลาสเก่า: บนเครื่องเห็นสี แต่เครื่องจริงไม่เห็น
production = เครื่อง server จริงที่ผู้ใช้เข้า · ตอนเอาแอปขึ้นไป (deploy) ไฟล์ CSS จะถูกสร้างใหม่ตั้งแต่ศูนย์ด้วย assets:precompile (บท 127) ·
กับดักต่อ string ข้างบนสีหายตั้งแต่บนเครื่องเรา (ทดลองผ่าน bin/dev) — แต่มีอยู่ทางหนึ่งที่บนเครื่องยังเห็นสี ·
ผมลองลำดับนี้โดยเปิด bin/dev ค้างไว้ตลอด:
| ขั้น | โค้ดใน _post.html.erb | grep -c pink / blue | สีบนจอ |
|---|---|---|---|
| 1 | ต่อ string (เปิด bin/dev ตอนนี้) | 0 / 0 | ไม่มีสี |
| 2 | เปลี่ยนเป็นชื่อเต็ม | 1 / 1 | มีสี |
| 3 | เปลี่ยนกลับเป็นต่อ string (เช่น AI "จัดโค้ดให้สั้นลง") | 1 / 1 | ยังมีสี |
| 4 | เหมือนขั้น 3 · สั่ง bin/rails assets:precompile (แบบขึ้นเครื่องจริง · บท 127) | 0 / 0 ในไฟล์ public/assets/tailwind-…css | บน production ไม่มีสี |
ขั้น 3 คือตัวการ: ตัว watch ที่เปิดค้างอยู่เพิ่มคลาสใหม่ที่เพิ่งเห็น แต่ไม่ลบคลาสเก่าที่เคยเห็น จนกว่าจะปิดแล้วเปิดใหม่ · บนเครื่องเราจึงยังเห็นสีจากของที่ "จำไว้" ·
พอสร้างใหม่ตั้งแต่ศูนย์ (ปิดเปิด bin/dev · tailwindcss:build · precompile ตอน deploy) ของที่จำไว้หายหมด
บทเรียน: อย่าเชื่อสายตาบนเครื่องตัวเอง · ใช้ grep หลัง build ใหม่ทุกครั้งเป็นตัวตัดสิน
bin/rails server อย่างเดียว = เปิดครัวรับออร์เดอร์ แต่ฝ่ายจัดซื้อไม่ได้เข้ากะ · เชฟเพิ่มเมนูใหม่ในกระดาษ ไม่มีใครไปซื้อวัตถุดิบ จานออกมาขาดของ ·
bin/dev = เปิดครัวพร้อมฝ่ายจัดซื้อประจำกะที่ยืนเฝ้ากระดาษเมนู เห็นเมนูใหม่ปุ๊บวิ่งไปซื้อปั๊บ
ที่ไม่เหมือนกัน: ฝ่ายจัดซื้อประจำกะคนนี้ไม่เคยทิ้งของจนกว่าจะหมดกะ — เมนูที่ขีดฆ่าไปแล้ว วัตถุดิบยังอยู่ในตู้ (ขั้น 3 ในตารางข้างบน) · ร้านจริงถ้าเก็บของค้างไว้ก็แค่เปลืองที่ · แต่ของเราทำให้บนเครื่องเราหน้าตาดีกว่าของจริง ซึ่งอันตรายกว่า
สรุปคำสั่งที่ใช้บ่อยของเรื่อง CSS (รายการเต็มของ gem ดูได้ด้วย bin/rails -T tailwindcss):
| คำสั่ง | ใช้ตอนไหน |
|---|---|
bin/dev | ทำงานปกติทุกวัน — server + Tailwind watch |
bin/rails tailwindcss:build | สร้าง CSS ใหม่หนึ่งรอบ แล้วจบ · ใช้ตอนพิสูจน์ด้วย grep หรือหลัง clobber |
bin/rails assets:precompile | ลองเตรียมไฟล์แบบขึ้นเครื่องจริง (บท 127) · ลองแล้วต้อง clobber ทุกครั้ง |
bin/rails assets:clobber | ล้างของที่ precompile ไว้ — ลบ app/assets/builds/tailwind.css ไปด้วย ต้อง build ใหม่ (บท 127) |
bin/importmap json | ดูว่า JavaScript ชื่ออะไรชี้ไปไฟล์ไหน (บท 127) · ส่วน bin/importmap pin <ชื่อ> ไปโหลด library จากเน็ต ใช้เมื่อตั้งใจเพิ่มจริงเท่านั้น |
และใครทำอะไรในบล็อก B (สไลด์หน้า 33 ข้อ 2 · จับคู่ให้แล้ว):
| ชื่อ | หน้าที่ | ในร้านอาหาร |
|---|---|---|
| Propshaft | ติดเลขรุ่น (fingerprint) แล้วเสิร์ฟไฟล์ | พนักงานติดเลขรุ่นบนปกเมนูแล้ววางบนชั้น |
| importmap | แมปชื่อ library ไปหาไฟล์ | สมุดรายชื่อ "ชื่อเล่น → อยู่ชั้นไหน" |
| tailwindcss-rails | สร้าง CSS จากคลาสที่สแกนเจอ | ฝ่ายจัดซื้อที่อ่านเมนูบนกระดาษ |
| daisyUI | ชั้น component + ธีม บน Tailwind | ชุดจานชามสำเร็จรูป + ป้ายธีมร้าน |
bin/dev·Procfile.dev- คำสั่งเปิดหลายโปรแกรมพร้อมกันตามรายการใน
Procfile.dev(ตอนนี้มี web กับ css) · พิมพ์บอก AI ได้ว่า "I'm running bin/dev (web + tailwindcss:watch), but my new class still has no style" — มันจะได้ไม่ตอบว่า "ลืมรัน watch หรือเปล่า" - watch mode
- โหมดที่โปรแกรมเฝ้าไฟล์แล้วทำงานใหม่ทุกครั้งที่ไฟล์เปลี่ยน · ของ Tailwind เพิ่มคลาสใหม่ได้ แต่ไม่ลบคลาสเก่าจนกว่าจะเริ่มใหม่ · ถาม senior ได้ว่า "ก่อน deploy เราตรวจ CSS จาก build ที่สร้างใหม่หมดหรือยัง"
เอาไปใช้กับ AI — ประโยคใน prompt และ checklist ตรวจงาน
ตอนนี้คุณทำเองเป็นแล้ว · เวลาสั่ง AI ทำงานหน้าตา (Lab บท 129 จะทำจริง) ให้แนบ @prompts/00-context.md เสมอ (มีบรรทัด "ห้ามสร้าง tailwind.config.js" อยู่แล้ว) ·
และใส่ข้อห้ามนี้ในส่วน "ห้าม" ของ prompt (โครง 4 ส่วนจากบท 89):
ห้าม:
- สร้าง tailwind.config.js หรือ package.json / npm install
- เปลี่ยน @plugin "./daisyui.mjs" เป็น @plugin "daisyui"
- ต่อชื่อคลาสจากตัวแปร (เช่น text-<%= color %>-500 หรือ "bg-#{x}-100")
ให้เขียนชื่อคลาสเต็มทุกตัว แล้วเลือกด้วยเงื่อนไข
AI ส่งงานมาแล้ว ตรวจรับด้วย 4 ข้อนี้ (ทุกข้อเป็นคำสั่งที่คุณรันเป็นแล้วในบทนี้):
ls tailwind.config.js package.json→ ต้องขึ้นNo such file or directoryทั้งคู่git grep -n -E '[a-z]-(<%=|#\{)' app/views→ ต้องไม่มีผลลัพธ์bin/rails tailwindcss:buildแล้วgrep -cคลาสใหม่ที่ AI ใส่มาสัก 2–3 ตัว → ต้องได้ 1 ขึ้นไป- เปลี่ยน
data-themeเป็นdarkใน Console → ตัวหนังสือยังอ่านออกทุกจุด (ถ้าจม = AI ใช้สีตายตัวแทนสีตามหน้าที่)
🎯 ภารกิจ (~10 นาที) — หากับดัก แก้ แล้วพิสูจน์ด้วย grep
สมมติ AI เขียนป้าย "ใหม่ / เก่า" ให้บทความ (ใหม่ = เขียนภายใน 7 วัน) · ก็อปไปวางใน app/views/posts/_post.html.erb ใต้บรรทัด </p> ตัวแรก (บรรทัดที่ปิดชื่อหมวดกับเวลา):
<% tone = post.created_at > 7.days.ago ? "amber" : "slate" %>
<span class="<%= "mt-2 inline-block rounded px-2 text-xs bg-#{tone}-100" %>">
<%= post.created_at > 7.days.ago ? "ใหม่" : "เก่า" %>
</span>
7.days.ago = "เวลาเมื่อ 7 วันก่อน" (ของแถมจาก Rails) · post.created_at > 7.days.ago = "เขียนหลังจาก 7 วันก่อน" = ใหม่ · #{tone} = เสียบค่าตัวแปรกลาง string ของ Ruby
บรรทัด 2 มีเครื่องหมายคำพูดซ้อนกัน (class="<%= "…" %>") ไม่พัง เพราะ Rails ทำส่วน <%= %> เสร็จก่อนส่ง · เบราว์เซอร์ได้แค่ class="mt-2 … bg-amber-100"
- สั่ง
bin/rails tailwindcss:buildแล้วนับ:grep -c 'bg-amber-100' app/assets/builds/tailwind.cssและgrep -c 'bg-slate-100' app/assets/builds/tailwind.css - สั่ง
git grep -n -E '[a-z]-(<%=|#\{)' app/views· ควรเจอบรรทัดที่เพิ่งวาง - แก้ให้ถูก (ใบ้: ตั้งตัวแปร
tone_classที่เก็บชื่อคลาสเต็ม แล้วย้ายคลาสที่ตายตัวออกมานอก<%= %>) · build ใหม่ แล้วนับด้วยgrep -cอีกรอบ - เปิด
bin/devแล้วดู/posts
ควรเห็น: ข้อ 1 ได้ 0 ทั้งคู่ · ข้อ 2 เจอ 1 บรรทัด · ข้อ 3 ได้ 1 ทั้งคู่ และคำสั่ง git grep ไม่พิมพ์อะไร ·
ข้อ 4 ป้าย "ใหม่" พื้นเหลืองอ่อน "เก่า" พื้นเทาอ่อน (หน้าแรกของเรามี "ใหม่" 6 ชิ้น "เก่า" 4 ชิ้น · ของคุณขึ้นกับวันที่รัน seeds)
ถ้าเจออาการนี้ แปลว่า…
| เห็นอะไร | แปลว่า |
|---|---|
ข้อ 1 ได้ 1 ทั้งที่ยังไม่แก้ |
มีไฟล์อื่นในโปรเจกต์เขียนคำนั้นไว้เต็ม ๆ (เช่นไฟล์จดโน้ตที่ไม่อยู่ใน .gitignore) · หาด้วย git grep --untracked -l 'bg-amber-100' (--untracked = ค้นไฟล์ที่ git ยังไม่ได้เก็บด้วย · -l = บอกแค่ชื่อไฟล์ · ผมลองวางไฟล์ notes.txt ที่มีคำนี้ไว้ ได้ build 1 และคำสั่งนี้ชี้ไปที่ notes.txt) · หรือคุณเปิด bin/dev ค้างไว้ตอนลองแบบชื่อเต็ม (watch จำ) → ปิดแล้ว build ใหม่ |
grep -c ได้ 1 แล้ว แต่หน้าเว็บยังไม่มีสี |
มี public/assets ค้างจากการลอง precompile (บท 127) → bin/rails assets:clobber → bin/rails tailwindcss:build → เปิด server ใหม่ |
| ป้ายมีสี แต่ไม่มีมุมมน ไม่มีระยะ | คุณย้าย rounded px-2 text-xs ไปต่อ string อีกที่ (เช่น "rounded-#{size}") · รันคำสั่ง git grep ของข้อ 2 อีกรอบ |
$ bin/rails tailwindcss:build
$ grep -c 'bg-amber-100' app/assets/builds/tailwind.css
0
$ grep -c 'bg-slate-100' app/assets/builds/tailwind.css
0
ข้อสังเกต: rounded px-2 text-xs ที่อยู่ใน string เดียวกันได้กฎครบ (ผมเช็คแล้ว ก่อนวางโค้ดนี้ทั้งสามตัวไม่มีในไฟล์ CSS) — ฝ่ายจัดซื้ออ่านทุกคำที่หน้าตาเป็นชื่อคลาส แม้อยู่ใน string ของ Ruby · พังเฉพาะคำที่ถูกต่อ
โค้ดที่แก้แล้ว:
<% tone_class = post.created_at > 7.days.ago ? "bg-amber-100" : "bg-slate-100" %>
<span class="mt-2 inline-block rounded px-2 text-xs <%= tone_class %>">
<%= post.created_at > 7.days.ago ? "ใหม่" : "เก่า" %>
</span>
$ bin/rails tailwindcss:build
$ grep -c 'bg-amber-100' app/assets/builds/tailwind.css
1
$ grep -c 'bg-slate-100' app/assets/builds/tailwind.css
1
Chrome วัดพื้นป้าย "ใหม่" ได้ oklch(0.962 0.059 95.617) (เหลืองอ่อน) · "เก่า" ได้ oklch(0.968 0.007 247.896) (เทาอมฟ้าอ่อน)
ก่อนไปบท 129 ล้างของทดลองทั้งบท:
git restore .
rm -f tailwind.config.js
bin/rails assets:clobber
bin/rails tailwindcss:build
git status
git status ต้องขึ้น nothing to commit, working tree clean · (rm -f ลบไฟล์ทดลองที่ git ยังไม่รู้จัก ถ้าคุณสร้างตามหัวข้อ v3/v4 · ไม่มีก็ไม่เป็นไร)
1. (จากสไลด์หน้า 33 ข้อ 1) ในแอปนี้เขียน <% level = post.created_at > 7.days.ago ? "red" : "gray" %> แล้ว <span class="badge bg-<%= level %>-100"> · ผลคือป้ายบทความเก่าพื้นเทาขึ้นปกติ แต่ป้ายบทความใหม่ไม่มีพื้นแดง · เพราะอะไร?
ตัวเลือกที่ 3 ถูก · ผมลอง build แล้ว grep -c '\.bg-gray-100{' ได้ 1 (มาจากปุ่ม "Show" "Edit" ในหน้าหมวดที่ scaffold สร้างไว้ และปุ่ม "แก้ไข" "กลับไปหน้ารวม" ในหน้าบทความ) · \.bg-red-100{ ได้ 0 · สีเทาจึงรอดเพราะโชค สีแดงไม่รอด · แก้ด้วยการเขียน "bg-red-100" "bg-gray-100" เต็มทั้งคำแล้วเลือก
ตัวเลือกที่ 1 ผิด — daisyUI ไม่มีกฎห้ามแบบนั้น คลาสแดงไม่มาเพราะไม่มีในไฟล์ CSS ·
ตัวเลือกที่ 2 ผิด — HTML ที่ได้มี bg-red-100 จริงสำหรับบทความใหม่ Ruby คำนวณถูก · ปัญหาอยู่ที่ฝั่ง CSS ·
ตัวเลือกที่ 4 ผิด — สีอย่าง red-100 เป็นของ Tailwind ไม่ได้มากับธีมของ daisyUI
จุดที่ต้องจำ: สีจากการต่อ string บางตัว "ดูเหมือนใช้ได้" เพราะบังเอิญมีชื่อเต็มในไฟล์อื่น · ตัดสินด้วย grep หลัง build ไม่ใช่ด้วยสายตา
2. (จากสไลด์หน้า 33 ข้อ 3) ขอให้ AI เพิ่มสีประจำร้าน มันตอบว่า "สร้างไฟล์ tailwind.config.js ใส่ content กับ plugins: [require("daisyui")]" · ควรทำยังไง?
ตัวเลือกที่ 2 ถูก · ผมสร้าง tailwind.config.js ที่ตั้งสี mint แล้วใช้ text-mint · build ผ่านแต่ grep -c text-mint ได้ 0 · ส่วน --color-brand ใน @theme ได้ .text-brand จริง
ตัวเลือกที่ 1 ผิด — v4 ไม่อ่านไฟล์นี้อัตโนมัติเลย ไม่ใช่อ่านก่อนหลัง ·
ตัวเลือกที่ 3 ผิด — ถ้าลบ @plugin daisyUI จะหายทั้งชุด เพราะไฟล์ JS นั้นไม่ถูกอ่าน · และแอปเราไม่มี package daisyui ให้ require ·
ตัวเลือกที่ 4 ผิด — ไม่มี error เลย นี่แหละที่ทำให้กับดักนี้น่ากลัว
จุดที่ต้องจำ: Tailwind v4 ตั้งค่าใน CSS · ไวยากรณ์ v3 มักไม่ error แค่ไม่มีผล · เขียนห้ามไว้ใน 00-context.md ตั้งแต่แรก
3. หน้าหนึ่งมีปุ่ม class="btn btn-primary" กับปุ่ม class="bg-blue-600 text-white" · เปลี่ยน <html> เป็น data-theme="dark" · ปุ่มไหนเปลี่ยนสี?
ตัวเลือกที่ 1 ถูก · วัดใน Chrome จริง: btn-primary เปลี่ยนจาก oklch(0.45 0.24 277.023) เป็น oklch(0.58 0.233 277.117) · bg-blue-600 เป็น oklch(0.546 0.245 262.881) ทั้งสองธีม
ตัวเลือกที่ 2 ผิด — กลับกัน สีที่ล็อกคือเลขสีตายตัวของ Tailwind ·
ตัวเลือกที่ 3 ผิด — data-theme เปลี่ยนเฉพาะสีที่อ้างชื่อตามหน้าที่ (primary · base-100 …) · สีตายตัวไม่สนธีม ·
ตัวเลือกที่ 4 ผิด — ทุกธีมที่เปิดไว้อยู่ในไฟล์ CSS แล้ว เบราว์เซอร์สลับได้ทันทีไม่ต้อง build
จุดที่ต้องจำ: อยากให้ธีมมืดใช้ได้ทั้งหน้า ต้องใช้สีตามหน้าที่ · สีตายตัวคือจุดที่ "จม" เวลาสลับธีม
4. เพื่อนเปิด bin/rails server ค้างไว้ เพิ่ม text-orange-600 ให้หัวข้อ · curl เห็นคลาสใน HTML แล้ว แต่หน้าจอไม่ส้ม · สาเหตุที่น่าจะเป็นที่สุดคืออะไร?
ตัวเลือกที่ 4 ถูก · ผมลองแล้ว: HTML มี text-orange-600 แต่ grep -c ในไฟล์ CSS ได้ 0 · พอเปิด bin/dev ได้ 1
ตัวเลือกที่ 1 ผิด — curl เห็นคลาสใหม่แล้ว แปลว่า HTML ใหม่มาแล้ว ปัญหาอยู่ที่ไฟล์ CSS ·
ตัวเลือกที่ 2 ผิด — สีมาตรฐานอย่าง orange-600 มีให้อยู่แล้ว @theme ใช้เพิ่มสีของเราเอง ·
ตัวเลือกที่ 3 ผิด — ตอน development Rails อ่าน view ใหม่ทุก request อยู่แล้ว ไม่ต้องรีสตาร์ต (HTML ที่มีคลาสใหม่คือหลักฐาน)
จุดที่ต้องจำ: HTML มีคลาส แต่ไม่มีผล → ถามก่อนเลยว่า "เปิด bin/dev อยู่ไหม" แล้วพิสูจน์ด้วย grep -c
ถ้าเปิดสไลด์ดูประกอบ
| หน้า | สไลด์เขียน | ของจริง (tailwindcss-rails 4.7 · daisyUI 5.7.50) | ต้องสนใจไหม |
|---|---|---|---|
| 29 | ไฟล์ app/assets/stylesheets/application.tailwind.css |
แอปนี้ใช้ app/assets/tailwind/application.css (ที่อยู่ของ tailwindcss-rails รุ่นใหม่) |
ใช่ — แก้ไฟล์ที่ถูก |
| 29–30 | @plugin "daisyui"; |
daisyUI ถูก vendor เป็นไฟล์ · ต้องเขียน @plugin "./daisyui.mjs"; · พิมพ์ตามสไลด์ได้ Can't resolve 'daisyui' |
ใช่ — หัวข้อ daisyUI |
| 30 | themes: light --default, dark --prefersdark, synthwave; |
ใช้ได้จริง · light + dark เป็นค่าเริ่มต้นอยู่แล้ว (ไม่เขียนก็ได้ CSS เหมือนกันทุกไบต์) | รู้ไว้ |
| 31 | "บนเครื่องเห็นสี เพราะ dev สร้างทุกคลาส" | bin/dev สแกนแบบเดียวกับ build จริง สีหายตั้งแต่บนเครื่อง · ที่ "เห็นบนเครื่อง" มาจาก watch จำคลาสเก่า หรือชื่อเต็มบังเอิญมีในไฟล์อื่น |
รู้ไว้ — หัวข้อกับดัก |
| 31 | text-<%= color %>-500 กับ red/green |
ในแอปนี้สีขึ้น เพราะ scaffold มี text-red-500 text-green-500 เต็ม ๆ อยู่แล้ว · ลองกับสีอื่นจึงเห็นบั๊ก |
รู้ไว้ |
| 32 | "90% คือลืมรัน bin/dev" | ตัวเลขไม่ได้วัด · อาการตรงกับที่เราลอง | ไม่ต้อง |
อ้างอิง
- Tailwind CSS — Detecting classes in source files —
อ่าน Dynamic class names (กับดักต่อ string ตัวอย่างเดียวกับบทนี้) · Which files are scanned (ทำไมไฟล์ใน
.gitignoreไม่ถูกอ่าน) · Ignoring specific paths (@source not) - Tailwind CSS — Theme variables —
ตาราง Theme variable namespaces (
--color-*→ คลาสอะไรบ้าง) และหัวข้อ Referencing in JavaScript ถ้าอยากอ่านค่าสีจาก JS - Tailwind CSS — Upgrade guide —
ตาราง Renamed utilities (
shadow-sm→shadow-xsฯลฯ) และ Using a JavaScript config file (ยืนยันว่า v4 ไม่หาtailwind.config.jsเอง) - daisyUI — Themes —
ไวยากรณ์
themes:·--default·--prefersdarkและ List of themes (35 ธีม) - daisyUI — Colors —
รายชื่อสีตามหน้าที่ (
primary·base-100·base-content…) และเหตุผลที่ควรใช้แทนสีตายตัว - rails/tailwindcss-rails (README) —
หัวข้อ Building for production และส่วนที่อธิบาย
tailwindcss:watch·bin/dev