วิธีลดขนาด bundle size ของ Tailwind
Tailwind CSS นั้นใช้หลักการของ Atomic CSS ที่หนึ่งคลาสมีความหมายเดียว เช่น คลาส text-center
มีความหมายเดียวกับการใช้ text-align: center
ปัญหาของการระบุคลาสในลักษณะนี้ให้กับแต่ละอีลีเมนต์คือชื่อคลาสที่ใช้มีขนาดค่อนข้างยาวและซ้ำไปซ้ำมา ในทุกครั้งที่เราเรียกใช้คลาสเหล่านี้ในอีลีเมนต์อื่น ๆ
จากรูปกลุ่มของปุ่มข้างต้น ถ้าเขียนเป็นคลาสผ่าน Tailwind CSS จะได้โค้ดดังนี้
1<div class="m-1 space-x-1">2 <button class="py-1 px-2 bg-green-400 text-white rounded">Pfizer</button>3 <button class="py-1 px-2 bg-green-400 text-white rounded">Moderna</button>4 <button class="py-1 px-2 bg-green-400 text-white rounded">Astrazeneca</button>5 <button class="py-1 px-2 bg-red-400 text-white rounded">Sinovac</button>6</div>
เราจะมีวิธีไหนบ้างที่สามารถลดขนาดชื่อคลาสเพื่อทำให้ผลลัพธ์สุดท้ายของโค้ดเรามีขนาด bundle size ที่ลดลง?
Mangle CSS Class
จากโค้ดของปุ่มข้างต้นพบว่าโค้ดของเรามีหลายคลาสที่ใช้ซ้ำกันโดยแต่ละคลาสมีชื่อค่อนข้างยาวเสียด้วย
เช่น bg-green-400
และ text-white
เป็นต้น
จะดีกว่าไหมถ้าเราสามารถหาวิธีย่อชื่อของแต่ละคลาสลงได้ เมื่อชื่อของคลาสสั้นลงขนาดโค้ดเราก็จะลดลงตาม
เราสามารถทำสิ่งนี้ให้เกิดขึ้นจริงได้บน Tailwind CSS ด้วยการใช้ mangle-css-class-webpack-plugin ที่เป็น Plugin ของ Webpack
ต่อไปนี้เป็นขั้นตอนการใช้งาน Plugin ดังกล่าวกับ React (ที่สร้างผ่าน Create React App)
- ติดตั้ง Plugin นี้ผ่านคำสั่งคือ
1yarn add -D mangle-css-class-webpack-plugin
- ภายหลังจากการสร้างโปรเจคบน Create React App แล้วให้ทำการติดตั้ง Craco ผ่านคำสั่งคือ
1yarn add -D @craco/craco
- ทำการสร้างไฟล์ craco.config.js เพื่อตั้งค่าการใช้งาน Webpack ในที่นี้เราจะทำการลดขนาดของชื่อคลาสแต่ละตัวของ Tailwind ให้สั้นลงเฉพาะการทำงานบน Production (หลัง build โปรเจคแล้ว) ส่วนบน development เราจะไม่ดำเนินการใด ๆ
1// craco.config.js2const { whenProd } = require('@craco/craco')3const MangleCssClassPlugin = require('mangle-css-class-webpack-plugin')45const twRegEx =6 '(([a-zA-Z-:]*)[\\\\\\\\]*:)*([\\\\\\\\]*!)?tw-[a-zA-Z-]([a-zA-Z0-9-]*([\\\\\\\\]*(\\%|\\#|\\.|\\[|\\]))*)*'78module.exports = {9 style: {10 postcss: {11 plugins: [require('tailwindcss'), require('autoprefixer')],12 },13 },14 webpack: {15 plugins: {16 add: [17 ...whenProd(18 () => [19 new MangleCssClassPlugin({20 classNameRegExp: twRegEx,21 log: true,22 }),23 ],24 []25 ),26 ],27 },28 },29}
จากนั้นทำการอัพเดตส่วนของ scripts ใน package.json ดังนี้
1{2 "scripts": {3 "start": "craco start",4 "build": "craco build",5 "test": "craco test",6 "eject": "react-scripts eject"7 }8}
การทำงานของ MangleCssClassPlugin เราต้องทำการระบุว่าให้ตัว Plugin จัดการลดขนาดคลาสที่มีรูปแบบใด ให้เราทำการระบุรูปแบบคลาสของ Tailwind ลงไป ฉะนั้นแล้วคลาสรูปแบบอื่นที่ไม่ใช่ของ Tailwind จะไม่ได้รับผลกระทบ
RegEx ตามบรรทัดที่ 5 นั้นจะเข้าได้กับคลาสของ Tailwind โดยแต่ละคลาสเราจะต้องเติม tw-
เข้าไปนำหน้าเสียก่อนถึงใช้งานได้
- เพื่อให้ Tailwind CSS เข้าใจว่าแต่ละคลาสที่เคยใช้ได้บนระบบ ตอนนี้จะต้องเติม
tw-
เสียก่อนเพื่อให้กลับมาใช้ได้อีกครั้ง เราต้องทำการระบุค่า prefix ในไฟล์ตั้งค่าของ Tailwind คือ tailwind.config.js
1module.exports = {2 purge: [],3 darkMode: false,4 prefix: 'tw-',5 theme: {6 extend: {},7 },8 variants: {9 extend: {},10 },11 plugins: [],12}
- ตอนนี้รูปแบบของโค้ดจะต้องเปลี่ยนไปด้วยการเติม
tw-
ให้แต่ละคลาสของ Tailwind โค้ดล่างนี้เป็นการใช้งานกับ React
1<div className="tw-m-1 tw-space-x-1">2 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">3 Pfizer4 </button>5 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">6 Moderna7 </button>8 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">9 Astrazeneca10 </button>11 <button className="tw-py-1 tw-px-2 tw-bg-red-400 tw-text-white tw-rounded">12 Sinovac13 </button>14</div>
- ทำการ build โปรเจคด้วยการออกคำสั่ง
yarn build
ขั้นตอนนี้จะเห็นจาก log ว่า Plugin ของเราได้ทำการเปลี่ยนชื่อคลาสของ Tailwind ให้เป็นชื่อที่สั้นลงแล้ว
เมื่อเราทำการเปิดเว็บเพื่อเข้าถึงโค้ดที่ผ่านการ build แล้ว จะได้คลาสใหม่ที่สั้นลงส่งผลให้ bundle size เล็กลงตามด้วย
หลีกเลี่ยงการสร้างคลาสใหม่ เปลี่ยนไปสร้างคอมโพแนนท์แทน
กรณีที่เราใช้คลาสกับกลุ่มของปุ่มดังต่อไปนี้
1<>2 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">3 Pfizer4 </button>5 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">6 Moderna7 </button>8 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">9 Astrazeneca10 </button>11</>
พบว่าโค้ดของเราจะอ่านยากและยาวเพราะเราใช้ชื่อคลาสซ้ำกันเยอะ บางคนอาจแก้ไขปัญหานี้ด้วยการสร้างคลาสใหม่
1.btn {2 @apply tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded;3}
ตัวอย่างข้างต้นเป็นการสร้างคลาสใหม่ชื่อ btn โดยมีการแสดงผลตามแต่ค่าของคลาสใน Tailwind ที่ถูกระบุหลัง @apply วิธีเรียกใช้งานใหม่คือ
1<>2 <button className="btn">Pfizer</button>3 <button className="btn">Moderna</button>4 <button className="btn">Astrazeneca</button>5</>
แม้ว่าวิธีนี้จะทำให้โค้ดอ่านง่ายขึ้น แต่ก็ทำให้สูญเสียคุณสมบัติความเป็น Atomic CSS ด้วยเช่นกัน นอกจากนี้คลาสใหม่คือ btn จะทำให้ขนาด bundle size ของ CSS เพิ่มขึ้นด้วย เนื่องจาก Tailwind จะคัดลอกค่าของแต่ละคลาสที่ใช้ในการสร้าง btn มาผนวกเป็นค่าของคลาส btn เสียเอง
1/* ค่าของ btn ที่ได้จากการใช้ @apply */2.btn {3 border-radius: 0.25rem;4 --tw-bg-opacity: 1;5 background-color: rgba(52, 211, 153, var(--tw-bg-opacity));6 padding-left: 0.5rem;7 padding-right: 0.5rem;8 padding-top: 0.25rem;9 padding-bottom: 0.25rem;10 --tw-text-opacity: 1;11 color: rgba(255, 255, 255, var(--tw-text-opacity));12}
วิธีที่ดีกว่าคือการสร้างคอมโพแนนท์ขึ้นมาใหม่แล้วทำการเรียกใช้คอมโพแนนท์นั้นซ้ำในทุกครั้งที่ต้องการสร้างปุ่ม
1const Button: FC = ({ children }) => {2 return (3 <button className="tw-py-1 tw-px-2 tw-bg-green-400 tw-text-white tw-rounded">4 {children}5 </button>6 )7}89// เรียกใช้ปุ่ม10;<>11 <Buttin>Pfizer</Buttin>12 <Buttin>Moderna</Buttin>13 <Buttin>Astrazeneca</Buttin>14</>
ลบคลาสที่ไม่ได้ใช้
ไม่ใช่ทุกคลาสของ Tailwind CSS ที่โปรเจคเราจะได้ใช้ ดังนั้นเราจึงควรลบคลาสที่ไม่ได้ใช้ออกจากผลลัพธ์สุดท้ายของเรา เราสามารถบอก Tailwind ให้ตรวจสอบคลาสจากไฟล์ index.html และไฟล์นามสกุล js, jsx, ts และ tsx หาก Tailwind ไม่พบคลาสใดในไฟล์เหล่านั้น Tailwind จะไม่ทำการรวมคลาสเหล่านั้นในผลลัพธ์ของ CSS ด้วยเมื่อเรา build โปรเจค เราสามารถทำสิ่งนี้ได้ด้วยการระบุค่าใน purge ของการตั้งค่าในไฟล์ tailwind.config.js
1module.exports = {2 purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],3 darkMode: false,4 prefix: 'tw-',5 theme: {6 extend: {},7 },8 variants: {9 extend: {},10 },11 plugins: [],12}
อย่าสร้างการใช้งานคลาสของ Tailwind แบบ Dynamic
สมมติปุ่มของเราสามารถระบุค่าสีได้สองแบบคือ success
สำหรับพื้นหลังสีเขียว และ danger
สำหรับพื้นหลังสีแดง
(กรณีไม่รุค่าสีเริ่มต้นเป็น success) โค้ดการเรียกใช้งานเป็นดังนี้
1<div className="tw-m-1 tw-space-x-1">2 <Button>Pfizer</Button>3 <Button>Moderna</Button>4 <Button>Astrazeneca</Button>5 <Button color="danger">Sinovac</Button>6</div>
เราอาจใช้พร็อพเพอร์ตี้ color เป็นเงื่อนไขในการพิจารณาเลือกค่าสีมาแสดงผลเป็นพื้นหลังดังโค้ดบรรทัดที่ 6
1type ButtonProps = {2 color?: 'success' | 'danger',3}45const Button: FC<ButtonProps> = ({ color = 'success', children }) => {6 const bgColor = `tw-bg-${color === 'success' ? 'green' : 'red'}-400`78 return (9 <button className={`tw-py-1 tw-px-2 ${bgColor} tw-text-white tw-rounded`}>10 {children}11 </button>12 )13}
การสร้างคลาสโดยใช้ส่วนหนึ่งส่วนใดของพร็อพเพอร์ตี้มาต่อข้อความเพื่อเรียกใช้คลาสของ Tailwind นั้นจะทำให้คำสั่ง purge ของ Tailwind ทำงานได้ไม่ถูกต้อง วิธีที่ดีกว่าคือการสร้างคลาสแบบตายตัวขึ้นมาตามค่าพร็อพเพอร์ตี้แทน ดังนี้
1const bgColor = color === 'success' ? 'tw-bg-green-400' : 'tw-bg-red-400'
สารบัญ
- Mangle CSS Class
- หลีกเลี่ยงการสร้างคลาสใหม่ เปลี่ยนไปสร้างคอมโพแนนท์แทน
- ลบคลาสที่ไม่ได้ใช้
- อย่าสร้างการใช้งานคลาสของ Tailwind แบบ Dynamic