เจาะลึกการแสดงภาพNG: LayoutNG

เอียน คิลแพทริก
เอียน คิลแพทริก
โคจิ อิชิ
Koji Ishi

ผมชื่อ Ian Kilpatrick เป็นหัวหน้าวิศวกร ของทีมเลย์เอาต์ของ Blink ร่วมกับ Koji Ishii ก่อนทำงานในทีม Blink ผมเป็นวิศวกรฟรอนท์เอนด์ (ก่อนที่ Google จะรับบทบาทเป็น "วิศวกรฟรอนท์เอนด์") โดยมี การสร้างฟีเจอร์ต่างๆ ภายใน Google เอกสาร, ไดรฟ์ และ Gmail หลังจากผ่านไปประมาณ 5 ปี ผมก็ลองเดิมพันครั้งใหญ่โดยสลับไปทำงานกับทีม Blink เรียนรู้ C++ เกี่ยวกับงานอย่างมีประสิทธิภาพ และพยายามเพิ่มฐานของโค้ด Blink ที่ซับซ้อนขึ้น ถึงตอนนี้เราเข้าใจเนื้อหาเพียงส่วนน้อยเท่านั้น เรารู้สึกซาบซึ้งเป็นอย่างยิ่งสำหรับช่วงเวลาที่ได้มอบให้แก่เราในช่วงเวลานี้ ผมรู้สึกอุ่นใจมากที่ "การกู้คืนวิศวกรฟรอนท์เอนด์" จำนวนมากทำให้ผมเปลี่ยนมาเป็น "วิศวกรเบราว์เซอร์" ก่อนผม

ประสบการณ์ที่ผ่านมาช่วยแนะนำฉันเป็นการส่วนตัวขณะอยู่ในทีม Blink ในฐานะวิศวกรฟรอนท์เอนด์ ฉันมักจะพบกับความไม่สอดคล้องของเบราว์เซอร์ ปัญหาด้านประสิทธิภาพ ข้อบกพร่องในการแสดงผล และฟีเจอร์ที่ขาดหายไป LayoutNG เป็นโอกาสให้เราช่วยแก้ปัญหาเหล่านี้ในระบบเลย์เอาต์ของ Blink อย่างเป็นระบบ และแสดงถึงความพยายามของวิศวกรหลายคนในช่วงหลายปีที่ผ่านมา

ในโพสต์นี้ เราจะอธิบายว่าการเปลี่ยนแปลงสถาปัตยกรรมครั้งใหญ่เช่นนี้จะช่วยลดและลดข้อบกพร่องและปัญหาด้านประสิทธิภาพประเภทต่างๆ ได้อย่างไร

ภาพมุมมอง 30,000 ฟุตของสถาปัตยกรรมเครื่องมือเลย์เอาต์

ก่อนหน้านี้ แผนผังเลย์เอาต์ของ Blink คือสิ่งที่ฉันจะเรียกว่า "ต้นไม้แบบเปลี่ยนแปลงได้"

แสดงโครงสร้างตามที่อธิบายไว้ในข้อความต่อไปนี้

แต่ละวัตถุในแผนผังเลย์เอาต์มีข้อมูลอินพุต เช่น ขนาดที่พร้อมใช้งานซึ่งกำหนดโดยผู้ปกครอง ตำแหน่งของแบบลอย และข้อมูลเอาต์พุต ตัวอย่างเช่น ความกว้างและความสูงสุดท้ายของวัตถุ หรือตำแหน่ง x และ y

วัตถุเหล่านี้อยู่รอบๆ ระหว่างการแสดงภาพ เมื่อเปลี่ยนรูปแบบแล้ว เราทำเครื่องหมายวัตถุนั้นว่าสกปรก และต้นกำเนิดทั้งหมดที่อยู่บนต้นไม้ เมื่อช่วงการจัดวางของไปป์ไลน์การแสดงผลทำงาน เราจะทำความสะอาดต้นไม้ เดินบนวัตถุที่สกปรก แล้วเรียกใช้เลย์เอาต์เพื่อให้ได้สถานะที่สะอาด

เราพบว่าสถาปัตยกรรมนี้ทำให้เกิดปัญหาในหลายระดับ ซึ่งเราจะอธิบายต่อไปนี้ แต่ก่อนอื่น เรามาย้อนกลับไปและพิจารณาว่าอินพุตและเอาต์พุตของเลย์เอาต์มีอะไรบ้าง

การเรียกใช้เลย์เอาต์บนโหนดในต้นไม้นี้ใช้ "สไตล์และ DOM" และข้อจำกัดระดับบนสุดจากระบบเลย์เอาต์ระดับบนสุด (ตารางกริด บล็อก หรือ Flex) จะเรียกใช้อัลกอริทึมข้อจำกัดเลย์เอาต์และสร้างผลลัพธ์

โมเดลแนวคิดที่อธิบายไว้ก่อนหน้านี้

สถาปัตยกรรมใหม่ของเราทำให้โมเดลแนวคิดนี้ดูเป็นรูปธรรม เรายังมีแผนผังเลย์เอาต์ แต่ใช้เพื่อยึดอินพุตและเอาต์พุตของเลย์เอาต์เป็นหลัก สำหรับเอาต์พุตนี้ เราจะสร้างออบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ขึ้นมาใหม่ทั้งหมดซึ่งเรียกว่า แฟรกเมนต์ทรี

แผนผังส่วนย่อย

ผมได้พูดถึงแผนผังส่วนย่อยที่เปลี่ยนแปลงไม่ได้ก่อนหน้านี้ไปแล้ว โดยได้อธิบายถึงวิธีการออกแบบการนำส่วนขนาดใหญ่ของแผนผังก่อนหน้ามาใช้ซ้ำสำหรับเลย์เอาต์ที่เพิ่มขึ้น

นอกจากนี้ เรายังจัดเก็บออบเจ็กต์ข้อจำกัดระดับบนที่สร้างส่วนย่อยดังกล่าวด้วย เราใช้คีย์นี้เป็นคีย์แคช ซึ่งจะพูดถึงข้อมูลเพิ่มเติมด้านล่าง

นอกจากนี้ ระบบยังเขียนอัลกอริทึมเลย์เอาต์ในบรรทัด (แบบข้อความ) ใหม่เพื่อให้ตรงกับสถาปัตยกรรมใหม่ที่เปลี่ยนแปลงไม่ได้ ซึ่งไม่เพียงสร้าง การแสดงรายการแบบเดี่ยวที่เปลี่ยนแปลงไม่ได้ สำหรับเลย์เอาต์แบบอินไลน์ แต่ยังมีการแคชระดับย่อหน้าเพื่อให้รีเลย์เร็วขึ้น รูปร่างต่อย่อหน้าเพื่อใช้คุณลักษณะของแบบอักษรกับองค์ประกอบและคำ อัลกอริทึมแบบใหม่ของ Unicode แบบ 2 ทิศทางที่ใช้ ICU, การแก้ไขความถูกต้องจำนวนมาก และอีกมากมาย

ประเภทของข้อบกพร่องด้านเลย์เอาต์

ข้อบกพร่องด้านเลย์เอาต์ที่พูดถึงอย่างกว้างๆ แบ่งออกเป็น 4 หมวดหมู่ ซึ่งแต่ละประเภทมีสาเหตุที่แท้จริงต่างกัน

ความถูกต้อง

เมื่อนึกถึงข้อบกพร่องในระบบการแสดงผล เรามักจะนึกถึงความถูกต้อง ตัวอย่างเช่น "เบราว์เซอร์ A มีลักษณะการทำงานแบบ X ในขณะที่เบราว์เซอร์ B มีลักษณะการทำงานแบบ Y" หรือ "เบราว์เซอร์ A และ B ไม่ทำงาน" ก่อนหน้านี้เราใช้เวลาอย่างมาก และต้องต่อสู้กับระบบอย่างต่อเนื่อง โหมดการทำงานล้มเหลวโดยทั่วไปคือ การแก้ไขเป้าหมายสำหรับข้อบกพร่องหนึ่ง แต่พบว่าสัปดาห์ต่อมาเราได้ทำให้ส่วนอื่นในระบบเกิดปัญหา (ดูจะไม่เกี่ยวข้อง) ถดถอย

นี่เป็นสัญญาณของระบบที่ยุ่งเหยิงมาก ดังที่ได้อธิบายไว้ในโพสต์ก่อนหน้า สำหรับการจัดวางโดยเฉพาะ เราไม่ได้ทำสัญญาที่ชัดเจนระหว่างคลาสใดๆ ทำให้วิศวกรเบราว์เซอร์ต้องพึ่งพาสถานะที่ไม่ควร หรือตีความค่าบางอย่างผิดจากส่วนอื่นๆ ของระบบ

เช่น ช่วงหนึ่งเรามีข้อบกพร่องประมาณ 10 รายการในช่วงกว่า 1 ปี ที่เกี่ยวข้องกับเลย์เอาต์แบบยืดหยุ่น การแก้ไขแต่ละครั้งทำให้เกิดปัญหาด้านความถูกต้องหรือประสิทธิภาพในบางระบบ ทำให้เกิดข้อบกพร่องอีกอย่างหนึ่ง

เมื่อ LayoutNG กำหนดสัญญาระหว่างองค์ประกอบทั้งหมดในระบบเลย์เอาต์อย่างชัดเจน เราพบว่าเราสามารถใช้การเปลี่ยนแปลงได้อย่างมั่นใจมากขึ้น นอกจากนี้ เรายังได้รับประโยชน์อย่างเต็มที่จากโครงการการทดสอบแพลตฟอร์มเว็บ (WPT) ที่ยอดเยี่ยม ซึ่งช่วยให้หลายฝ่ายสามารถจัดทำชุดทดสอบเว็บที่ใช้ร่วมกันได้

ปัจจุบันเราพบว่าหากเราเผยแพร่การถดถอยที่แท้จริงในช่องทางที่มีความเสถียรของเรา ก็มักจะไม่มีการทดสอบที่เกี่ยวข้องในที่เก็บ WPT และไม่ได้เกิดจากความเข้าใจผิดเกี่ยวกับสัญญาคอมโพเนนต์ นอกจากนี้ ตามนโยบายการแก้ไขข้อบกพร่องแล้ว เรายังเพิ่มการทดสอบ WPT ใหม่อยู่เสมอเพื่อให้มั่นใจว่าเบราว์เซอร์ทุกรายการไม่ควรทำงานผิดพลาดเดิมอีก

ไม่ถูกต้อง

หากคุณเคยมีข้อบกพร่องลึกลับที่การปรับขนาดหน้าต่างเบราว์เซอร์หรือการสลับที่พร็อพเพอร์ตี้ CSS ทำให้ข้อบกพร่องนั้นหายไปได้อย่างน่าอัศจรรย์ แสดงว่าคุณพบปัญหาที่ระบุว่าไม่ถูกต้อง โครงสร้างที่เปลี่ยนแปลงได้บางส่วนอย่างมีประสิทธิภาพถือว่าสะอาด แต่เนื่องจากการเปลี่ยนแปลงบางอย่างในข้อจำกัดหลัก องค์ประกอบดังกล่าวไม่ได้แสดงผลที่ถูกต้อง

ซึ่งพบได้บ่อยมากกับโหมดเลย์เอาต์แบบ 2 ทาง (เดินผ่านแผนผังเลย์เอาต์ 2 ครั้งเพื่อระบุสถานะเลย์เอาต์สุดท้าย) ตามที่อธิบายด้านล่าง ก่อนหน้านี้โค้ดของเราจะมีลักษณะดังนี้

if (/* some very complicated statement */) {
  child->ForceLayout();
}

การแก้ไขข้อบกพร่องประเภทนี้ตามปกติแล้วมีดังนี้

if (/* some very complicated statement */ ||
    /* another very complicated statement */) {
  child->ForceLayout();
}

การแก้ไขปัญหาประเภทนี้มักทำให้ประสิทธิภาพถดถอยอย่างรุนแรง (ดูการแก้ไขมากเกินไปด้านล่าง) และแก้ไขเป็นเรื่องละเอียดอ่อนมาก

ในวันนี้ (ตามที่อธิบายไว้ข้างต้น) เรามีออบเจ็กต์ข้อจำกัดระดับบนที่เปลี่ยนแปลงไม่ได้ ซึ่งอธิบายอินพุตทั้งหมดจากเลย์เอาต์หลักไปยังไปยังระดับล่าง เราจัดเก็บรายการนี้ไว้กับแฟรกเมนต์ที่เปลี่ยนแปลงไม่ได้ที่เกิดขึ้น เราจึงมีส่วนกลางที่เราจะแยกอินพุต 2 รายการนี้เพื่อพิจารณาว่าบุตรหลานต้องมีบัตรผ่านสำหรับเลย์เอาต์อีกหรือไม่ ตรรกะที่แตกต่างกันนี้ซับซ้อน แต่ครบถ้วนสมบูรณ์ การแก้ไขข้อบกพร่องของปัญหาประเภทไม่ถูกต้องในระดับนี้มักจะส่งผลให้เกิดการตรวจสอบอินพุต 2 รายการด้วยตนเอง และตัดสินใจว่าอะไรในอินพุตที่เปลี่ยนแปลงจนต้องใช้เลย์เอาต์อื่น

การแก้ไขโค้ดที่แตกต่างกันนี้มักจะทำได้ง่ายๆ และสามารถทดสอบหน่วยได้อย่างง่ายดายด้วยความสะดวกในการสร้างวัตถุที่เป็นอิสระต่อกันเหล่านี้

การเปรียบเทียบรูปภาพความกว้างคงที่และความกว้างเปอร์เซ็นต์
องค์ประกอบความกว้าง/ความสูงคงที่จะไม่สนว่าขนาดที่ใช้ได้จะเพิ่มขึ้นหรือไม่ แต่จะเพิ่มความกว้าง/ความสูงแบบเปอร์เซ็นต์ ส่วนขนาดที่ใช้ได้จะแสดงในออบเจ็กต์ข้อจำกัดหลักและการเพิ่มประสิทธิภาพนี้เป็นส่วนหนึ่งของอัลกอริทึมความแตกต่าง

โค้ดความแตกต่างของตัวอย่างข้างต้นคือ

if (width.IsPercent()) {
  if (old_constraints.WidthPercentageSize() 
    != new_constraints.WidthPercentageSize())
   return kNeedsLayout;
}
if (height.IsPercent()) {
  if (old_constraints.HeightPercentageSize() 
    != new_constraints.HeightPercentageSize())
   return kNeedsLayout;
}

ไฮสเตเรซิส

ข้อบกพร่องประเภทนี้คล้ายกับการระบุว่าไม่ถูกต้อง โดยพื้นฐานแล้ว ในระบบก่อนหน้านี้ การทำให้เลย์เอาต์เป็นแบบเดิมเป็นเรื่องที่ทำได้ยากมาก ซึ่งก็คือการเรียกใช้เลย์เอาต์ที่มีอินพุตเดียวกันอีกครั้ง ซึ่งทำให้ได้เอาต์พุตเดียวกัน

ในตัวอย่างด้านล่าง เราเพียงแค่สลับคุณสมบัติ CSS ไปมาระหว่าง 2 ค่า อย่างไรก็ตาม ผลที่ได้คือสี่เหลี่ยมผืนผ้า "เติบโตอย่างไม่มีที่สิ้นสุด"

วิดีโอและการสาธิตแสดงข้อบกพร่องของ Hysteresis ใน Chrome 92 และเวอร์ชันที่เก่ากว่า ปัญหานี้ได้รับการแก้ไขใน Chrome 93

สำหรับแผนผังที่เปลี่ยนได้ก่อนหน้านี้ ทำให้ข้อบกพร่องแบบนี้เกิดขึ้นได้ง่ายมาก หากโค้ดอ่านขนาดหรือตำแหน่งของวัตถุผิดพลาดในเวลาหรือระยะที่ไม่ถูกต้อง (เช่น เราไม่ได้ "ล้าง" ขนาดหรือตำแหน่งก่อนหน้า) เราจะเพิ่มข้อบกพร่องของการไฮสเตรีสย่อยเล็กๆ น้อยๆ โดยทันที โดยทั่วไปแล้ว ข้อบกพร่องเหล่านี้จะไม่ปรากฏในการทดสอบ เนื่องจากการทดสอบส่วนใหญ่จะมุ่งเน้นที่เลย์เอาต์และการแสดงภาพเดียว ที่น่ากังวลยิ่งขึ้นไปอีกคือ แต่เราก็ตระหนักว่าจำเป็นต้องมีอุปสรรคบางอย่างนี้เพื่อทำให้โหมดเลย์เอาต์บางอย่างทำงานได้อย่างถูกต้อง เราพบข้อบกพร่องที่ต้องเพิ่มประสิทธิภาพเพื่อลบ Layout Pass ออก แต่ให้แนะนำ "ข้อบกพร่อง" เนื่องจากโหมดเลย์เอาต์ต้องใช้การส่ง 2 ครั้งเพื่อให้ได้ผลลัพธ์ที่ถูกต้อง

ต้นไม้ที่แสดงปัญหาที่อธิบายไว้ในข้อความก่อนหน้า
ระบบจะแสดงผลเลย์เอาต์ที่ไม่ใช่ตัวจริง ทั้งนี้ขึ้นอยู่กับข้อมูลผลลัพธ์เลย์เอาต์ก่อนหน้า

ด้วย LayoutNG เนื่องจากเรามีโครงสร้างข้อมูลอินพุตและเอาต์พุตที่ชัดเจน และการเข้าถึงสถานะก่อนหน้าไม่ได้รับอนุญาต เราจึงลดข้อบกพร่องประเภทนี้จากระบบการออกแบบในวงกว้าง

การระบุแหล่งที่มาและประสิทธิภาพมากเกินไป

ซึ่งเป็นสิ่งที่ตรงข้ามกับข้อบกพร่องในประเภทที่ไม่ถูกต้องในระดับที่ไม่ถูกต้องโดยตรง การแก้ไขข้อบกพร่องที่ไม่เกี่ยวกับเหตุผลที่ไม่ถูกต้องมักจะทำให้เกิดหน้าผาประสิทธิภาพ

เราต้องตัดสินใจยากๆ เพราะเรามักเลือกความถูกต้องมากกว่าประสิทธิภาพ ในส่วนถัดไป เราจะเจาะลึกวิธีที่เราบรรเทาปัญหาเกี่ยวกับประสิทธิภาพประเภทนี้

การเพิ่มขึ้นของเลย์เอาต์แบบ 2 ทางและหน้าผาประสิทธิภาพสูง

เลย์เอาต์แบบยืดหยุ่นและตารางกริดแสดงถึงการเปลี่ยนแปลงความโดดเด่นของเลย์เอาต์ในเว็บ อย่างไรก็ตาม โดยพื้นฐานแล้ว อัลกอริทึมเหล่านี้แตกต่างจากอัลกอริทึมเลย์เอาต์แบบบล็อกที่อยู่ก่อนหน้า

เลย์เอาต์แบบบล็อก (ในเกือบทุกกรณี) กําหนดให้เครื่องมือใช้เลย์เอาต์สําหรับโฆษณาย่อยทั้งหมดเพียงครั้งเดียวเท่านั้น วิธีนี้เป็นผลดีต่อประสิทธิภาพ แต่สุดท้ายแล้วต้องไม่แสดงออกอย่างที่นักพัฒนาเว็บต้องการ

เช่น คุณต้องการให้เด็กทั้งหมดขยายเป็นขนาดใหญ่สุด เลย์เอาต์หลัก (เฟล็กซ์หรือตารางกริด) จะใช้ Measurement Pass เพื่อระบุว่าเด็กแต่ละคนมีขนาดเท่าใด จากนั้นจึงส่งเลย์เอาต์เพื่อขยายเด็กทั้งหมดให้มีขนาดเท่านี้ ลักษณะการทำงานนี้เป็นค่าเริ่มต้นสำหรับทั้งเลย์เอาต์แบบยืดหยุ่นและตารางกริด

กล่อง 2 ชุด ชุดแรกแสดงขนาดภายในของช่องในมาตรวัด กล่องที่ 2 มีขนาดเท่ากันทั้งหมด

เลย์เอาต์แบบ 2 ช่องสัญญาณนี้เป็นแบบประสิทธิภาพที่ยอมรับได้ เนื่องจากผู้ใช้มักไม่ได้ฝังเลย์เอาต์แบบนี้ไว้ลึกๆ อย่างไรก็ตาม เราเริ่มเห็นปัญหาด้านประสิทธิภาพที่สำคัญเมื่อมีเนื้อหาที่ซับซ้อนเกิดขึ้น ถ้าคุณไม่ได้แคชผลลัพธ์ของระยะการวัดผล แผนผังเลย์เอาต์จะแทรกระหว่างสถานะการวัดกับสถานะเลย์เอาต์สุดท้าย

เลย์เอาต์แบบ 1, 2 และ 3 แบบ 1, 2 และ 3 ตามที่อธิบายไว้ในคำบรรยาย
ในรูปภาพด้านบน เรามีองค์ประกอบ <div> 3 อย่าง เลย์เอาต์แบบคลิกเดียว (เช่น เลย์เอาต์แบบบล็อก) จะมีโหนดเลย์เอาต์ 3 โหนด (ความซับซ้อน O(n)) อย่างไรก็ตาม สำหรับเลย์เอาต์แบบ 2 Pass (เช่น Flex หรือตารางกริด) อาจส่งผลให้การเข้าชม O(2n) ซับซ้อนขึ้นสำหรับตัวอย่างนี้
กราฟแสดงเวลาเลย์เอาต์ที่เพิ่มขึ้นแบบทวีคูณ
รูปภาพและการสาธิตนี้แสดงเลย์เอาต์แบบเลขชี้กำลังที่มีเลย์เอาต์แบบตารางกริด ปัญหานี้ได้รับการแก้ไขใน Chrome 93 เนื่องจากการย้าย Grid เข้าสู่สถาปัตยกรรมใหม่

ก่อนหน้านี้เราจะพยายามเพิ่มแคชที่เจาะจงมากๆ ลงในเลย์เอาต์แบบยืดหยุ่นและแบบตารางกริดเพื่อต่อสู้กับหน้าผาประสิทธิภาพประเภทนี้ วิธีนี้ได้ผล (และเราก้าวมาไกลมากด้วย Flex) แต่ก็ต้องต่อสู้กับข้อบกพร่องอยู่เรื่อยๆ โดยไม่ผ่านการแก้ไข

LayoutNG ช่วยให้เราสร้างโครงสร้างข้อมูลที่ชัดเจนสำหรับทั้งอินพุตและเอาต์พุตของเลย์เอาต์ และนอกเหนือจากนั้น เราได้สร้างแคชของการวัดผลและ Layout Pass แล้ว วิธีนี้กลับทำให้มีความซับซ้อนกลับคืนมา และส่งผลให้นักพัฒนาเว็บมีประสิทธิภาพในเชิงเส้นแบบคาดการณ์ได้ หากมีกรณีที่เลย์เอาต์ทำเลย์เอาต์แบบ 3 พาธ เราจะแคชข้อความนั้นไว้เช่นกัน ซึ่งอาจเปิดโอกาสในการนำโหมดเลย์เอาต์ขั้นสูงขึ้นอย่างปลอดภัยในตัวอย่างในอนาคตของการปลดล็อกการขยายการใช้งานของ RenderingNG อย่างเป็นพื้นฐาน ในบางกรณีเลย์เอาต์แบบตารางกริดอาจต้องใช้เลย์เอาต์แบบ 3 พาส แต่ขณะนี้เกิดขึ้นไม่บ่อยนัก

เราพบว่าเมื่อนักพัฒนาซอฟต์แวร์พบปัญหาด้านประสิทธิภาพโดยเฉพาะกับเลย์เอาต์ โดยปกติแล้วจะเกิดจากข้อบกพร่องด้านเวลาเลย์เอาต์แบบเอ็กซ์โปเนนเชียลมากกว่าอัตราการส่งข้อมูลดิบของขั้นตอนเลย์เอาต์ของไปป์ไลน์ หากการเปลี่ยนแปลงทีละน้อย (องค์ประกอบ 1 รายการที่เปลี่ยนพร็อพเพอร์ตี้ CSS รายการเดียว) ทำให้เลย์เอาต์ยาว 50-100 มิลลิวินาที ก็น่าจะเป็นข้อบกพร่องของเลย์เอาต์แบบเอ็กซ์โปเนนเชียล

ข้อมูลสรุป

เลย์เอาต์เป็นพื้นที่ที่ซับซ้อนมากๆ และเราไม่ได้ครอบคลุมรายละเอียดที่น่าสนใจทุกประเภท เช่น การเพิ่มประสิทธิภาพเลย์เอาต์แบบอินไลน์ (หรือก็คือการทำงานของระบบย่อยแบบแทรกในบรรทัดและข้อความทั้งหมด) และแม้แต่แนวคิดที่พูดถึงในจุดนี้ก็เป็นแค่เพียงการขูดพื้นเท่านั้น อย่างไรก็ตาม หวังว่าเราได้แสดงให้เห็นแล้วว่าการปรับปรุงสถาปัตยกรรมของระบบอย่างเป็นระบบสามารถทำให้เกิดประโยชน์มากกว่าในระยะยาวได้อย่างไร

แต่เรารู้ว่ายังมีงานอีกมากมายที่รออยู่ข้างหน้า เราทราบถึงคลาสของปัญหา (ทั้งด้านประสิทธิภาพและความถูกต้อง) ที่เรากำลังแก้ไขอยู่ และรู้สึกตื่นเต้นกับฟีเจอร์การออกแบบใหม่ๆ ที่จะเพิ่มเข้ามาใน CSS เราเชื่อว่าสถาปัตยกรรมของ LayoutNG จะช่วยให้การแก้ปัญหาเหล่านี้ปลอดภัยและควบคุมได้

รูปภาพ 1 รูป (คุณรู้ไหมว่าอันไหน!) โดย Una Kravets