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

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

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

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

รหัสการละเว้นข้อมูล

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

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

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

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

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

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

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

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

sourcesContent จะรวมอยู่ในทั้ง 2 แหล่งที่มาเดิมนี้ และ Chrome DevTools จะแสดงไฟล์เหล่านี้โดยค่าเริ่มต้นในโปรแกรมแก้ไขข้อบกพร่อง

  • เป็นไฟล์ในแผนผังแหล่งที่มา
  • ตามผลลัพธ์ในกล่องโต้ตอบ 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],
  ...
}

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

x_google_ignoreList ใน Angular

ตั้งแต่ Angular v14.1.0 เนื้อหาของโฟลเดอร์ node_modules และ webpack ได้รับการทําเครื่องหมายเป็น"to ignore"

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

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

เฟรมการเรียกใช้ที่ใช้งานง่ายใน Angular

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

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

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

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

ในอนาคต

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

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