4 คะแนน โดย GN⁺ 2024-03-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Jampack เป็นเครื่องมือ post-processing ที่รับผลลัพธ์จาก Static Site Generator มาเพื่อปรับปรุงประสบการณ์ผู้ใช้และคะแนน Core Web Vitals โดยไม่ใช่ bundler หรือ framework
  • มันแปลง <img> และ <picture> ใน HTML ให้เป็นรูปภาพแบบ responsive และเพิ่มฟอร์แมตอย่าง WebP·AVIF รวมถึง srcset, sizes, width, height, loading="lazy", decoding="async" เป็นต้น โดยอัตโนมัติ
  • รูปภาพจาก CDN สามารถทำให้ responsive ได้ด้วย srcset ที่อิงพารามิเตอร์ใน URL และรูปภาพภายนอกสามารถดาวน์โหลดมาไว้ใต้ _jampack แล้วแปลงเป็นรูปภาพโลคัลที่ปรับแต่งแล้วได้
  • แอสเซ็ตส่วน above-the-fold จะถูกจัดการด้วยลำดับความสำคัญสูง รูปขนาดเล็กจะ inline เข้าไปใน HTML ส่วนรูปภาพและ iframe ที่เป็น below-the-fold จะถูก lazy load
  • วิธีใช้งานคือรัน npx @divriots/jampack ./dist กับโฟลเดอร์ผลลัพธ์ build ของเว็บไซต์แบบสแตติก และใน second pass ยังจัดการบีบอัด CSS·JS·HTML·SVG·รูปภาพด้วย

บทบาทของ Jampack

  • Jampack รับผลลัพธ์ที่สร้างโดย Static Site Generator หรือ SSG มาเป็นอินพุต แล้วปรับแต่งเว็บไซต์แบบสแตติกให้เหมาะสมที่สุด
  • เป้าหมายคือปรับปรุงประสบการณ์ผู้ใช้และคะแนน Core Web Vitals
  • README แยก Jampack ไว้ชัดเจนว่า “ไม่ใช่ bundler และไม่ใช่ framework”
  • บทความแนะนำมีให้ที่ Read the introduction blog post

การปรับแต่งรูปภาพ

  • <img> ทั่วไปจะถูกแปลงเป็นรูปภาพแบบ responsive
    • สร้างไฟล์ WebP จาก src ต้นฉบับและเพิ่ม srcset
    • เพิ่มแอตทริบิวต์อย่าง sizes="100vw", loading="lazy", decoding="async", width, height
  • องค์ประกอบ <picture> จะถูกเปลี่ยนเป็นโครงสร้าง responsive ที่รองรับหลายฟอร์แมตของรูปภาพ
    • เพิ่ม <source type="image/avif"> สำหรับ AVIF
    • เพิ่ม <source type="image/webp"> สำหรับ WebP
    • ใน <img> ต้นฉบับก็จะมี srcset, sizes, loading, decoding, width, height ด้วย
  • ฟีเจอร์ปรับแต่งรูปภาพมีต่อในเอกสาร optimize-images แต่ลิงก์ใน README เป็น relative path

การจัดการรูปภาพจาก CDN และภายนอก

  • รูปภาพจาก CDN สามารถเพิ่ม srcset แบบ responsive ได้โดยยังคงใช้ URL ระยะไกลเดิม
    • ตัวอย่างคือเพิ่มพารามิเตอร์ w, fit=min, auto=format ให้กับ URL รูปภาพของ Unsplash เพื่อสร้างตัวเลือกหลายขนาดความกว้าง
    • ในรูปต้นฉบับจะมีการเพิ่ม loading="lazy", decoding="async", sizes="100vw" ด้วย
  • รูปภาพภายนอก สามารถดาวน์โหลดมาก่อนแล้วเปลี่ยนเป็นไฟล์โลคัลที่ปรับแต่งแล้วได้
    • ตัวอย่างคือเปลี่ยนรูป Unsplash ภายนอกให้เป็นพาธอย่าง _jampack/ab99b9d280ce4cf7cfc810b59f3a7739.jpg.webp
    • รูปภาพที่แปลงแล้วจะมี width, height, srcset, sizes, loading, decoding

Above-the-fold และการปรับแต่ง CSS·ลิงก์

  • Jampack ปรับแต่งแอสเซ็ต above-the-fold แยกต่างหาก
    • รูปภาพจะถูกโหลดด้วยลำดับความสำคัญที่สูงกว่า
    • รูปภาพขนาดเล็กจะถูก embed ลงใน HTML
  • แอสเซ็ต below-the-fold จะถูก lazy load
    • รูปภาพและ iframe เป็นเป้าหมายของการ lazy load
  • Critical CSS จะถูก inline ลงใน HTML
    • จุดประสงค์คือเพื่อหลีกเลี่ยง FOUC ที่อาจเกิดขึ้นระหว่างการดาวน์โหลดและ parse stylesheet
    • CSS ที่เหลือจะถูกโหลดแบบหน่วงเวลา
  • การ prefetch ลิงก์เป็นฟีเจอร์สำหรับทำให้การย้ายหน้าในอนาคตเร็วขึ้น
    • ใช้ quicklink เพื่อจัดการแบบไดนามิกเมื่อลิงก์เข้ามาอยู่ใน viewport

การบีบอัดแอสเซ็ตและวิธีรัน

  • Jampack จะบีบอัดแอสเซ็ตทั้งหมดที่ยังไม่ได้แตะต้องใน second pass โดยคงชื่อและฟอร์แมตเดิมไว้
  • เครื่องมือบีบอัดตามนามสกุลมีดังนี้
  • เมื่อเว็บไซต์แบบสแตติกอยู่ในโฟลเดอร์ dist ให้รันด้วยคำสั่งต่อไปนี้
npx @divriots/jampack ./dist
  • ตัวเลือกเพิ่มเติมดูได้ที่ CLI options

กรณีใช้งานและความหมายของชื่อ

1 ความคิดเห็น

 
GN⁺ 2024-03-26
ความคิดเห็นบน Hacker News
  • เป็นเครื่องมือที่กำลังหาอยู่พอดี ผมเคยเขียนสคริปต์เองบนพื้นฐานของ Sharp เพื่อทำ การปรับแต่งรูปภาพให้เหมาะสม แบบนี้ แต่ Jampack มาแทนที่ได้หมดและทำงานได้ดีกว่ามาก
    หลังจาก build ไซต์ static ด้วย Quarto แล้วรัน Jampack ขนาดโฟลเดอร์ลดลง 32% และยังไม่เห็นข้อเสียชัดเจน
    ตาม PageSpeed Insights ก่อนใช้ Jampack บนมือถือได้ performance 52, accessibility 73, best practices 100, SEO 85 ส่วนเดสก์ท็อปได้ performance 90, accessibility 75, best practices 100, SEO 82
    หลังใช้งานได้มือถือ performance 49, accessibility 80, best practices 100, SEO 92 และเดสก์ท็อป performance 85, accessibility 82, best practices 100, SEO 91

    • คะแนนของ Lighthouse และ PageSpeed Insights อาจแกว่งได้ เวลาทำการเปรียบเทียบประสิทธิภาพแบบนี้ ควรรันหลายครั้งแล้วดูค่ามัธยฐานจะดีกว่า
      มีข้อมูลที่บอกว่า “ค่ามัธยฐานของคะแนน Lighthouse จากการรัน 5 ครั้งเสถียรกว่าการรันครั้งเดียวสองเท่า”: https://developers.google.com/web/tools/lighthouse/variabili...
    • ดีใจที่ชอบนะ แต่ผมคิดว่า ตัวชี้วัดประสิทธิภาพ น่าจะดีขึ้นกว่านี้ ถ้าไม่รบกวน อยากให้แชร์ผลลัพธ์ไซต์ static ก่อนใช้ Jampack มาดูหน่อย
      georges [at] divriots [dot] com
  • นึกถึง โมดูล PageSpeed สำหรับ Apache และ Nginx: https://developers.google.com/speed/pagespeed/module

    • เท่าที่รู้ โปรเจกต์นั้นไม่ได้ดูแลต่อแล้ว
      GitHub repository ก็ถูกเก็บเข้าคลังแล้ว: https://github.com/apache/incubator-pagespeed-ngx
      หรือว่ามันย้ายไปที่อื่นแล้ว?
  • ว้าว อันนี้ค่อนข้างถูกใจเลย จะลองใช้ดู
    ถ้ามีใครรู้สึกว่ามันไม่ดี อยากให้ช่วยชี้ข้อบกพร่องหน่อย สำหรับผมมันดูคล้ายกับการคอมไพล์ C เป็น assembly ที่ปรับแต่งสุดขีด และเป็นเครื่องมือที่ช่วยทำงานที่เราไม่อยากทำเองได้อย่างแน่นอน

    • ถ้าต้อง deploy สิ่งที่เหมือน assembly ที่ปรับแต่งสุดขีด สำหรับ HTML และ CSS ผมก็ไม่แน่ใจว่าเรากำลังไปในทิศทางที่ถูกไหม
      ผมคิดว่าถ้าเราเขียน HTML และ CSS ที่เรียบง่ายและตรงไปตรงมาที่สุด เบราว์เซอร์บนทุกอุปกรณ์ก็ควร render ได้ดีอยู่แล้ว
      ถ้าจำเป็นต้อง deploy ผลลัพธ์ที่ถูกปรับแต่งระดับนั้นจริง ๆ ก็น่าจะข้าม HTML กับ CSS ไปเลย แล้วให้ deploy WebAssembly ที่ปรับแต่งสูง และให้ developer ใช้ภาษาที่ต้องการแทนจะดีกว่า
  • คงจะดีถ้ามีวิธี subset ฟอนต์ตาม ช่วง Unicode ของ output จาก SSG และ fix แกน OpenType โดยอิงจาก font-feature-settings ที่นิยามไว้ใน CSS

    • ใช่ มีเรื่องเจ๋ง ๆ ที่ทำกับฟอนต์ได้อีกเยอะ ใน TODO มีงานเพิ่ม ฟอนต์ระบบสำรอง ที่มี metrics ถูกต้องแบบอัตโนมัติ เพื่อปรับปรุง CLS โดยอัตโนมัติ
      ไม่แน่ใจว่านี่คือ “การ fix แกน OpenType ตาม font-feature-settings” ที่พูดถึงหรือเปล่า หรือหมายถึงอย่างอื่น
      อยากทำการ optimize การ subset ฟอนต์เหมือนกัน แต่ยังไม่แน่ใจว่าจะช่วยได้มากแค่ไหน อยากรู้ว่าเคยลองทำด้วยมือมาก่อนไหม
    • ถ้าใช้ฟอนต์ของเบราว์เซอร์/ระบบ ก็ optimize ให้ ขนาดฟอนต์เป็น 0 ได้อยู่แล้ว ไม่แน่ใจว่าจำเป็นต้องทำไหม
  • แนวคิดในการระบุ critical CSS ที่ควร inline แทนที่จะอยู่ใน stylesheet แยก น่าสนใจดี
    ผมหวังว่าจะมีวิธีแยก critical CSS กับ non-critical CSS ตามหลักการได้ เช่น เอฟเฟกต์การโต้ตอบของผู้ใช้อย่าง :hover ให้ถือว่าเป็น non-critical เสมอ
    แต่ไลบรารีที่ใช้เป็นวิธี render หน้าแล้วเดาให้ดีที่สุดว่ากฎไหนควรถือว่า critical เลยรู้สึกเสียดายนิดหน่อย: https://github.com/GoogleChromeLabs/critters

    • ถ้า CSS น้อยกว่า 50KB ก็ inline ไปเลยได้ ถ้ามี CSS เกิน 50KB แปลว่าน่าจะมีอะไรผิดทางแล้ว
      แน่นอนว่าถ้า inline ฟอนต์ด้วยก็พอเข้าใจได้ แต่ถ้าเฉพาะ style อื่น ๆ เกิน 50KB โดยทั่วไปถือว่ามาผิดทาง
      พูดจริง ๆ คือ inline ให้ประสิทธิภาพดีมาก แม้เทียบกับ cache ที่อุ่นแล้ว และจุดคุ้มทุนที่ stylesheet หรือ script ภายนอกจะดีกว่านั้นสูงกว่าที่คิด โดยมาตรฐานตลาดทั่วไปอาจขึ้นไปถึงหลายร้อย KB
      แนวคิด critical CSS ให้ความรู้สึกเหมือนเป็น แนวทางแบบยอมแพ้ ที่พยายามเอาประสิทธิภาพที่เสียไปกลับคืนมานิดหน่อย แทนที่จะแก้ปัญหาต้นตอ
      แต่ทั้งนี้นี่ไม่ใช่วิธีเชิงระบบ เป็นแค่การตัดสินจากประสบการณ์และการสังเกตแบบเบา ๆ ถ้ามีใครวัดแนวคิดนี้ให้จริงจังกว่านี้ก็คงดี แต่คงไม่ใช่ผมที่จะทำ
  • อันนี้ดูเหมือนครอบคลุมหลายกรณีการใช้งานที่ทำให้คนเลือก SSG และปลั๊กอิน ตั้งแต่แรก โดยเฉพาะถ้าเลือก Astro หรือ Eleventy
    มีเหตุผลอะไรไหมที่จะชอบให้มันเป็นขั้นตอนหลัง build แยกต่างหาก? แม้จะทำให้ rebuild ระหว่างพัฒนาเร็วขึ้น แต่ก็ดูเหมือนเป็นการแลกกับความเสี่ยงที่จะพลาด bug ละเอียด ๆ จากการเพิ่มอย่างประกาศ width ของรูปภาพ

  • ในฐานะคนที่ไม่ชอบทำ layout หน้าเว็บและปฏิเสธที่จะเรียน แต่บางครั้งก็ต้องทำ เครื่องมือนี้ดูดีมาก

  • ดูดีนะ แต่ส่วนตัวไม่ชอบเวลาต้องรอรูปภาพเมื่อ scroll หน้าไป ใต้ fold
    โดยค่าเริ่มต้น หลังจากเนื้อหาช่วงแรกโหลดเสร็จ มันโหลดเนื้อหาที่เหลือด้านล่างใน background ไหม?

    • ไม่ใช่ มันใช้ native lazy loading ของเบราว์เซอร์ เบราว์เซอร์หลัก ๆ จะตีความ attribute loading="lazy" ว่า “อย่าโหลดรูป/iframe นี้จนกว่ามันจะใกล้ปรากฏให้เห็น”: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/im...
      แต่เพราะใส่ aspect ratio แบบ inline ไว้แล้ว layout จึงไม่เปลี่ยนหลังโหลดเสร็จ ถือว่าหลีกเลี่ยงบาปใหญ่ที่สุดของ lazy loading ได้
    • อย่างที่ @lelandfe ชี้ไว้ Jampack ใช้ loading="lazy" แบบ native ของเบราว์เซอร์
      ตอนนี้ยังไม่มีวิธีเปลี่ยนพฤติกรรมนี้ แต่สามารถเพิ่ม option ให้ preload รูปภาพใต้ fold ใน background หลังจากโหลดทั้งหน้าเสร็จได้ เป็นไอเดียที่ดีทีเดียว
      แค่กังวลว่าจะไปโหลดรูปภาพที่ไม่จำเป็นตรงท้ายหน้าเข้า แต่ถ้าเป็น option ให้แต่ละคนเปิดหรือปิดเองได้ก็น่าจะโอเค
  • static site generator ที่ใช้ใน production มีอะไรบ้าง? ดูเหมือนเครื่องมือนี้จะช่วย optimize output ได้อีก
    เช่น เมื่อวานผมใช้เวลาทั้งวันทำตามตัวอย่างเพื่อแปลงเว็บไซต์ Divjoy React เป็น HTML ธรรมดาแล้วเสิร์ฟจาก S3 bucket ไม่คิดว่าจะยากขนาดนี้ และตอนนี้ก็ยังงงอยู่
    ตามอุดมคติ อยากได้อะไรสักอย่างที่ deploy ไป S3 bucket อัตโนมัติและเชื่อม domain ให้ด้วย เจ็บตรงที่จ่ายเงินไปแล้ว developer หายไป ส่วน Discord ก็ถูกทิ้งร้าง นี่แหละเหตุผลที่ผมมักชอบ FOSS มากกว่า

    • Hugo, Zola, Jekyll ประมาณนี้
      พวกนี้มีฟีเจอร์บางส่วนแบบนี้อยู่บ้างในระดับหนึ่ง
  • ในโปรเจกต์หนึ่งของผมไม่ได้เปลี่ยนแปลงมากนัก เช่น ขนาด bundle รวมลดลง แต่ขนาด gzip เพิ่มขึ้น สรุปแล้วจริง ๆ เป็นผลเสียสุทธิสำหรับผม
    ถึงอย่างนั้น การปรับปรุง CSS ดูเหมือนจะช่วยได้จริง
    ไอเดียดูยอดเยี่ยม และถ้าโปรเจกต์มีรูปภาพก็น่าจะช่วยได้

    • ถ้าไม่มีรูปภาพ ประโยชน์ก็จำกัดจริง ๆ อีกอย่าง เพราะมันปรับปรุง compatibility ของเบราว์เซอร์ให้อัตโนมัติ ขนาด CSS สุดท้ายจึงอาจใหญ่ขึ้นได้
      ถ้าตั้งค่า browserlist เป็นสตริงว่าง ก็ปิดฟีเจอร์นี้ได้: https://jampack.divriots.com/features/browser-compatibility/