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

ประสบการณ์การแก้ไขข้อบกพร่องที่ได้รับการปรับปรุง

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

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

รหัสรายชื่อที่ละเว้น

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

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

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

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

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

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

ด้านล่างนี้เป็นการแมปแหล่งที่มาของไฟล์ 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 สำหรับแหล่งที่มาดั้งเดิมทั้ง 2 แหล่งเหล่านี้ และเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome จะแสดงไฟล์เหล่านี้โดยค่าเริ่มต้นในโปรแกรมแก้ไขข้อบกพร่อง

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

มีข้อมูลเพิ่มเติมอีก 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 มีการทำเครื่องหมายเป็น "to ignore"

การดำเนินการดังกล่าวเกิดจากการเปลี่ยนแปลงใน angular-cli โดยการสร้างปลั๊กอินที่เชื่อมต่อเข้ากับโมดูล Compiler ของ 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 การดำเนินการนี้เป็นค่าเริ่มต้นในกรณีดังกล่าว นักพัฒนาแอปจึงตรวจสอบเรื่องนี้ได้อยู่แล้ว แต่ในโปรเจ็กต์ที่ซับซ้อนขึ้น ปัญหานั้นไม่ง่ายนัก โดยเฉพาะเมื่อใช้เฟรมเวิร์กที่มีกลไกการกำหนดเวลาขั้นสูง เช่น เฟรมเวิร์กที่ติดตามโซน จัดคิวงานที่กำหนดเอง หรือแบ่งการอัปเดตออกเป็นหลายหน่วยที่ทำงานเมื่อเวลาผ่านไป

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

API การติดแท็กสแต็กแบบไม่พร้อมกัน

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

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

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

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

ซึ่งทำได้โดยใช้เมธอด console ใหม่ชื่อ console.createTask() ซึ่งมี Async Stack Scheduling 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);

การดำเนินการแบบไม่พร้อมกันอาจมีการซ้อนกันและ "สาเหตุหลัก" จะปรากฏในสแต็กเทรซตามลำดับ

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

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 DevTools รองรับการเปลี่ยนชื่อฟังก์ชันเหล่านี้ผ่านการแมปแหล่งที่มา หากการแมปแหล่งที่มามีรายการชื่อสําหรับจุดเริ่มต้นของขอบเขตฟังก์ชัน (กล่าวคือ วงเล็บด้านซ้ายของรายการพารามิเตอร์) เฟรมการเรียกใช้ควรแสดงชื่อนั้นในสแต็กเทรซ

เฟรมการโทรที่เป็นกันเองใน Angular

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

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

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

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

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

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

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