กรณีศึกษา: การแก้ไขข้อบกพร่องของ Angular ที่ดียิ่งขึ้นด้วยเครื่องมือสำหรับนักพัฒนาเว็บ

ประสบการณ์การแก้ไขข้อบกพร่องที่ดียิ่งขึ้น

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

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

โค้ดการละเว้นข้อมูล

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

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

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

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

ส่วนขยายการแมปแหล่งที่มา x_google_ignoreList

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

ด้านล่างคือการแมปแหล่งที่มาของไฟล์ out.js ที่สร้างขึ้น มี sources ดั้งเดิม 2 รายการที่มีส่วนในการสร้างไฟล์เอาต์พุต: foo.js และ lib.js นโยบายแรกคือสิ่งที่นักพัฒนาเว็บไซต์เขียนไว้ ส่วนนโยบายหลังคือเฟรมเวิร์กที่ใช้

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "lib.js"],
  "sourcesContent": ["...", "..."],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}

มี sourcesContent รวมอยู่ด้วยสำหรับแหล่งที่มาต้นฉบับเหล่านี้และเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome จะแสดงไฟล์เหล่านี้โดยค่าเริ่มต้นใน Debugger

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

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

{
  ...
  "sources": ["foo.js", "lib.js"],
  "x_google_ignoreList": [1],
  ...
}

ช่อง x_google_ignoreList ใหม่มีดัชนีเดียวที่อ้างถึงอาร์เรย์ sources: 1 การดำเนินการนี้ระบุว่าภูมิภาคที่แมปกับ lib.js เป็นโค้ดของบุคคลที่สามที่ควรเพิ่มลงในรายการละเว้นโดยอัตโนมัติ

ในตัวอย่างที่ซับซ้อนยิ่งขึ้นที่แสดงด้านล่าง ดัชนี 2, 4 และ 5 ได้ระบุว่าภูมิภาคที่แมปกับ lib1.ts, lib2.coffee และ hmr.js เป็นโค้ดของบุคคลที่สามทั้งหมดที่ควรเพิ่มลงในรายการละเว้นโดยอัตโนมัติ

{
  ...
  "sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
  "x_google_ignoreList": [2, 4, 5],
  ...
}

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

x_google_ignoreList ใน Angular

ตั้งแต่ Angular v14.1.0 เนื้อหาของโฟลเดอร์ node_modules และ webpack มีสถานะเป็น "เพื่อละเว้น"

การดำเนินการดังกล่าวเกิดขึ้นได้ด้วยการเปลี่ยนแปลงใน angular-cli โดยการสร้างปลั๊กอินที่เกี่ยวเนื่องกับโมดูล Compiler ของ Webpack

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

const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];

for (const [index, path] of map.sources.entries()) {
  if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
    ignoreList.push(index);
  }
}

map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));

สแต็กเทรซที่ลิงก์

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

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

ในการแก้ปัญหานี้ เครื่องมือสำหรับนักพัฒนาเว็บแสดงกลไกที่เรียกว่า “Async Stack Tagging API” ในออบเจ็กต์ console ซึ่งช่วยให้นักพัฒนาเฟรมเวิร์กสามารถแนะนำทั้งตำแหน่งที่มีการกำหนดเวลาการดำเนินการและตำแหน่งที่ดำเนินการเหล่านี้

Async Stack Tagging API

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

สแต็กเทรซของโค้ดที่เรียกใช้แบบไม่พร้อมกันบางส่วนที่ไม่มีข้อมูลเกี่ยวกับเวลาที่กำหนดเวลา โดยจะแสดงเฉพาะสแต็กเทรซที่เริ่มต้นจาก "requestAnimationFrame" แต่ไม่เก็บข้อมูลจากเวลาที่กำหนดเวลา

เมื่อใช้การติดแท็กสแต็กแบบไม่พร้อมกัน คุณจะระบุบริบทนี้ได้และสแต็กเทรซมีลักษณะดังนี้

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

โดยใช้เมธอด console ใหม่ชื่อ console.createTask() ซึ่งมี Async Stack Tagging API ไว้ให้ มีลายเซ็นดังนี้

interface Console {
  createTask(name: string): Task;
}

interface Task {
  run<T>(f: () => T): T;
}

การเรียกใช้ console.createTask() จะส่งกลับอินสแตนซ์ Task ซึ่งคุณสามารถใช้เพื่อเรียกใช้โค้ดอะซิงโครนัสในภายหลังได้

// Task Creation
const task = console.createTask(name);

// Task Execution
task.run(f);

นอกจากนี้ การดำเนินการแบบอะซิงโครนัสยังฝังซ้อนกันได้ และ "สาเหตุหลัก" จะแสดงในสแต็กเทรซตามลำดับ

คุณจะเรียกใช้งานกี่ครั้งก็ได้และเพย์โหลดงานอาจแตกต่างกันไปในแต่ละการเรียกใช้ ระบบจะจดจำสแต็กการเรียกใช้ในเว็บไซต์การตั้งเวลาจนกว่าจะเก็บรวบรวมออบเจ็กต์งานไม่ได้อีกต่อไป

Async Stack Tagging API ใน Angular

ใน Angular มีการเปลี่ยนแปลงกับ NgZone ซึ่งเป็นบริบทการดำเนินการของ Angular ที่ยังคงอยู่ในงานที่ไม่พร้อมกัน

เมื่อกำหนดเวลางาน ระบบจะใช้ console.createTask() เมื่อพร้อมใช้งาน ระบบจะจัดเก็บอินสแตนซ์ Task ที่ได้ไว้สำหรับการใช้งานเพิ่มเติม เมื่อเรียกใช้งาน NgZone จะใช้อินสแตนซ์ Task ที่จัดเก็บไว้เพื่อเรียกใช้

การเปลี่ยนแปลงเหล่านี้ไปยัง NgZone 0.11.8 ของ Angular ผ่านคำขอพุล #46693 และ #46958

เฟรมการโทรที่เป็นมิตร

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

ใน Angular คุณไม่เห็นเฟรมการเรียกใช้ที่มีชื่อ เช่น AppComponent_Template_app_button_handleClick_1_listener ในสแต็กเทรซ

ภาพหน้าจอของสแต็กเทรซที่มีชื่อฟังก์ชันที่สร้างขึ้นโดยอัตโนมัติ

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

เฟรมการโทรที่เป็นมิตรใน Angular

การเปลี่ยนชื่อเฟรมการโทรใน Angular เป็นการดำเนินการอย่างต่อเนื่อง เราคาดว่าการปรับปรุงนี้จะค่อยๆ เพิ่มขึ้นเมื่อเวลาผ่านไป

ขณะแยกวิเคราะห์เทมเพลต HTML ที่ผู้เขียนได้เขียน คอมไพเลอร์ Angular จะสร้างโค้ด TypeScript ซึ่งเปลี่ยนไปเป็นโค้ด JavaScript ที่เบราว์เซอร์โหลดและเรียกใช้ในที่สุด

ในกระบวนการสร้างโค้ดนี้ จะมีการสร้างการแมปแหล่งที่มาขึ้นด้วย เรากำลังสำรวจวิธีการต่างๆ เพื่อใส่ชื่อฟังก์ชันในฟิลด์ "ชื่อ" ของการแมปแหล่งที่มา และอ้างอิงชื่อเหล่านั้นในการแมประหว่างโค้ดที่สร้างขึ้นและโค้ดต้นฉบับ

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

มองไปข้างหน้า

การใช้ Angular เป็นโปรแกรมนำร่องการทดสอบเพื่อยืนยันว่างานของเรานั้นให้ประสบการณ์ที่ยอดเยี่ยม เรายินดีรับฟังความคิดเห็นจากนักพัฒนาเฟรมเวิร์กและแสดงความคิดเห็นเกี่ยวกับประเด็นการขยายเหล่านี้

ยังมีด้านอื่นๆ ที่เราต้องการสำรวจเพิ่มเติม โดยเฉพาะอย่างยิ่ง วิธีปรับปรุงประสบการณ์การทำโปรไฟล์ในเครื่องมือสำหรับนักพัฒนาเว็บ