- PR รีแฟกเตอร์ ของคลังเก็บ Svelte ในปี 2023 ที่เปลี่ยนไปใช้โค้ดแบบอิง JSDoc ได้รับความสนใจจากกลุ่มที่ตั้งข้อกังขาต่อ TypeScript
- ฝั่ง Svelte อธิบายว่านี่ไม่ใช่จุดยืนแบบ ต่อต้าน TypeScript แต่เป็นส่วนหนึ่งของการ พึ่งพา TypeScript อย่างต่อเนื่อง
- บทความนี้ไม่ได้มอง JSDoc กับ TypeScript เป็นคู่ตรงข้าม แต่เน้นว่า ตัว JSDoc เองก็เป็นส่วนหนึ่งของ TypeScript
- TypeScript ทำหน้าที่เป็น เอนจิน IntelliSense ซึ่งรับผิดชอบทั้งการตีความคอมเมนต์ JSDoc และฟีเจอร์เติมโค้ดอัตโนมัติ
- JSDoc ให้ ความสามารถในการวิเคราะห์แบบสแตติกในระดับเดียวกัน โดยไม่ต้องมีขั้นตอน build และในโปรเจ็กต์ JavaScript ยุคใหม่ก็ทำหน้าที่แทบไม่ต่างจาก TypeScript
เบื้องหลัง PR ของ Svelte และประเด็นถกเถียง
- เดือนพฤษภาคม 2023 PR รีแฟกเตอร์ภายใน ของคลังเก็บ Svelte ขึ้นไปอยู่หน้าแรกของ Hacker News
- PR นี้เป็นการย้ายการประกาศชนิดในไฟล์
.ts ไปไว้ใน คอมเมนต์ JSDoc ของไฟล์ .js
- บางส่วนตีความว่านี่เป็นการปฏิเสธข้อดีของ TypeScript
- ผู้ก่อตั้ง Svelte อย่าง Rich Harris ได้อธิบายด้วยตนเองบน HN ว่า “นี่ไม่ใช่การต่อต้าน TypeScript”
- เขาระบุด้วยว่า ความมุ่งมั่นของ Svelte ต่อ TypeScript ยังคงแข็งแกร่ง
- หลังเหตุการณ์นี้ มีบทความเปรียบเทียบ “TypeScript vs JSDoc” จำนวนมากตามมา และแนวคิดที่มอง JSDoc ว่าเป็น “TypeScript ที่ไม่มีขั้นตอน build” ก็เริ่มแพร่หลาย
ต้นกำเนิดและแก่นแท้ของ TypeScript
- ช่วงปลายทศวรรษ 2000 ถึงต้นทศวรรษ 2010 JavaScript ถูกมองว่าเป็นภาษาที่ขาด การเติมโค้ดอัตโนมัติและความปลอดภัยของชนิดข้อมูล
- นักพัฒนาของ Microsoft รับมือด้วยการใช้ ScriptSharp แปลงโค้ด C# เป็น JS
- จากภูมิหลังนี้จึงเกิด TypeScript ซึ่งโดยแก่นแท้แล้วเริ่มต้นจากการเป็น เครื่องมือ build เพื่อปรับปรุงการพัฒนา JS
TypeScript คือ IntelliSense
- TypeScript ไม่ได้เป็นเพียงภาษา แต่ยังทำหน้าที่เป็น เอนจิน IntelliSense
- แม้จะไม่ได้ใช้ไฟล์
.ts ฟีเจอร์อย่างการเติมโค้ดอัตโนมัติ ข้อมูลพารามิเตอร์ และการค้นหาสัญลักษณ์ ก็ยังมาจากบริการภาษาของ TypeScript
- ในเอดิเตอร์ส่วนใหญ่ แม้ตอนเขียนโค้ด JS ก็มี บริการของ TypeScript ทำงานอยู่เบื้องหลัง
TypeScript คือ JSDoc
- บริการภาษาของ TypeScript ยังถูกใช้ในการ ตีความคอมเมนต์ JSDoc ด้วย
- ใน CHANGELOG ของ TypeScript มักมีรายการเพิ่มฟีเจอร์ที่เกี่ยวข้องกับ JSDoc อยู่เสมอ
- โปรเจ็กต์ที่อิง JSDoc ก็สามารถตั้งค่าผ่าน
tsconfig.json ได้ และใช้คำสั่ง tsc เพื่อ ตรวจสอบชนิดข้อมูล ได้
- ดังนั้น นักพัฒนาที่ใช้ JSDoc อยู่ก็เท่ากับว่า กำลังใช้ TypeScript อยู่แล้ว
ประสบการณ์กับโปรเจ็กต์ที่อิง JSDoc
- ผู้เขียนเล่าประสบการณ์การเขียนฟรอนต์เอนด์ของโปรเจ็กต์เดิมใหม่โดย อิงคำอธิบายชนิดด้วย JSDoc
- ยกเว้นฟีเจอร์รันไทม์อย่าง enum แล้ว รูปแบบการแสดงชนิดส่วนใหญ่ของ TypeScript สามารถทำได้ด้วย JSDoc
- แม้ generic จะมีไวยากรณ์ที่ซับซ้อนกว่าเล็กน้อย แต่ก็ทำให้ต้องใช้การอนุมานชนิดอย่างเต็มที่มากขึ้น
- ในโปรเจ็กต์ JSDoc เมื่อคลิกฟังก์ชันก็สามารถ ไปยังโค้ดจริงได้ทันที ทำให้ประสบการณ์พัฒนาดีขึ้น
- ระบบนิเวศเครื่องมือของ TypeScript ยังนำมาใช้ซ้ำกับโปรเจ็กต์ JSDoc ได้
- เช่น ไลบรารีที่สร้างชนิดจากสคีมา OpenAPI หรือ GraphQL ก็สามารถ สร้างชนิดในรูปคอมเมนต์ JSDoc ได้
บทสรุปและกรณีเพิ่มเติม
- JSDoc ไม่ใช่ทางเลือกแทน TypeScript แต่ ใช้ระบบการวิเคราะห์แบบสแตติกชุดเดียวกัน
- แม้จะตัดขั้นตอน build ออกไป แต่ก็ยังให้ ความปลอดภัยของชนิดข้อมูลในระดับเทียบเท่า
- นอกจากนี้ยังมีการกล่าวถึงกรณีที่ โปรเจ็กต์ webpack ย้ายมาใช้ JSDoc เช่นกัน
- ในฐานะผู้เชี่ยวชาญด้าน TypeScript ผู้เขียนจึงประกาศจุดยืนอย่างชัดเจนว่า “JSDoc คือ TypeScript”
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ได้สรุปสิ่งที่เรียนรู้จากการพัฒนาและบำรุงรักษาซอฟต์แวร์เว็บและหุ่นยนต์ด้วย Python/JavaScript มาหลายปี
ประเภทข้อมูลมีอยู่เสมอแม้จะไม่ได้ระบุไว้ และถ้าไม่ระบุ สุดท้ายมันก็จะมีอยู่แค่ในหัว
แต่ความจำของคนนั้นเลือนหายได้ง่าย และคนอื่นก็เข้าถึงได้ยาก
ดังนั้น การใส่ type คือวิธีทำเอกสารที่ยอดเยี่ยม
JSDoc และ TypeScript เป็นรูปแบบมาตรฐานในการแสดง type และทั้งคู่ก็มีข้อดีข้อเสีย
สิ่งสำคัญคือการนิยาม type ให้สม่ำเสมอและคาดเดาได้
ตัวตรวจสอบ type คือวิธีที่คอมพิวเตอร์พูดว่า “งั้นก็พิสูจน์มา”
ไม่ใช่ว่าทุกโปรแกรมจะต้องการการพิสูจน์ในระดับเดียวกัน และการพิสูจน์มากเกินไปก็อาจเป็นความสูญเปล่า
เพราะงั้นฉันจึงชอบภาษาที่ “พิสูจน์ได้” เท่าที่จำเป็น
โดยเฉพาะอย่างยิ่ง ‘คนอื่น’ นั้นรวมถึงตัวเราเองในอนาคตด้วย ซึ่งเป็นสิ่งที่ได้เรียนรู้อย่างหนักแน่นจากการทำงาน
Rust สามารถผ่อนข้อจำกัดได้ด้วยวิธีอย่าง unsafe, Arc, clone แต่ต้องแลกกับการเลือกอย่างชัดเจนว่าข้อจำกัดใดที่ยังไม่ได้รับการพิสูจน์
ในทางกลับกัน ภาษาที่ “ไม่ต้องพิสูจน์” มักทำให้ยากที่จะรู้ว่าเบื้องในใช้แนวทางไหน
แนวทางของ Rust ทำให้ช่วงแรกสามารถเขียนแบบหลวม ๆ คล้าย Python ได้ แต่ต่อมาจะได้เปรียบมากกว่ามากในด้าน ความอ่านง่ายและการขยายระบบ
ไม่ได้มีเจตนาจะเชียร์เครื่องมือใดเป็นพิเศษ แค่อยากเน้นว่าทั้งคู่ต่างก็เป็น วิธีแสดงออกของระบบ type
ภาษาที่มี static typing จัดการได้ง่ายกว่ามากในโปรเจกต์ทีม และจนถึงตอนนี้ถ้าเลือกได้ก็ยังชอบ static type
ถ้าดู type definition ของ TypeScript ที่ถูกเพิ่มเข้ามาทีหลังให้กับไลบรารี JS เดิม จะเห็นว่าความซับซ้อนสูงมาก
type ที่ผิดแค่จุดเดียวก็ทำให้คอมไพล์ทั้งชุดพังได้
สุดท้ายแล้ว ภาษาที่เป็น dynamic ก็ต้องใช้แบบ ‘รับผิดชอบด้วยตัวเอง’
ฉันชอบทุกอย่างที่ทำด้วย JavaScript ได้โดย ไม่ต้องมี build step
การผสมกันของ HTML/CSS สมัยใหม่, Web Components และ JSDoc นั้นถูกประเมินค่าต่ำเกินไป
มันอาจไม่เหมาะกับทุกคน แต่ฉันคิดว่ามันเป็นตัวเลือกของสแตกฟรอนต์เอนด์ที่ทันสมัยเพียงพอ
และด้วยฟีเจอร์อย่าง HMR ต้นทุนของ build step ก็ลดลงไปมากแล้ว
เพราะต้องผ่าน Vite หรือ Webpack ตลอด จึงไม่ค่อยรู้สึกถึงข้อดีของ JS แบบไม่มี build
อยากให้มีวิธีที่ทำคอมโพเนนต์ซับซ้อนได้ง่ายกว่านี้
ไม่ว่าจะเป็นการตามดู network request, กระโดดไปยังซอร์สโค้ดโดยตรง, ตั้ง breakpoint ฯลฯ การดีบักทำได้ตรงไปตรงมามากกว่า
ยิ่งระบบใหญ่ขึ้น สภาพแวดล้อมแบบนี้ก็ยิ่งช่วยได้มาก
ในยุคที่ SPA กำลังบูม JSDoc คือผู้กอบกู้ของการจัดการ type
หลังจากนั้น Google Closure Compiler ก็ปรากฏขึ้นและมอบ type safety บนฐานของ JSDoc ส่วน TypeScript ก็รองรับ (TS)JSDoc พร้อมไวยากรณ์ของตัวเอง
สุดท้ายชุมชนก็เลือก TypeScript และ Closure Compiler ก็เลือนหายไป
เพราะงั้น (TS)JSDoc จึงกลายเป็น โบราณวัตถุจากยุคที่ MS แข่งกับ Google
ตอนนี้ TS มีฟีเจอร์มากกว่ามาก เช่น generics, Enum, utility types, การทดสอบ type ของ Vitest, type guards ฯลฯ
ฉันใช้ทั้ง TS และ JSDoc ร่วมกัน — TS สำหรับโค้ด, JSDoc สำหรับเอกสาร (@link, @see, @deprecated, @example เป็นต้น)
ฟีเจอร์ TS ส่วนใหญ่ เช่น generics, utility types, type guards, การพาร์ส regular expression ฯลฯ ใช้ใน JSDoc ได้เหมือนกัน
ฉันเคยลองทำในโปรเจกต์ส่วนตัวด้วย JSDoc ล้วนทั้งหมดรวมถึง generics ด้วย
การบอกว่า (TS)JSDoc เป็นของตกยุคคือ ข้อมูลผิด จึงไม่ควรเผยแพร่โดยไม่ตรวจสอบ
หลายคนบอกว่ามี type จำนวนมากที่แสดงด้วย JSDoc ไม่ได้ แต่ฉันคิดว่าคงจะดีถ้ามันทำแบบทั้งภาษาได้เหมือน Flow
TypeScript เองก็ทำแบบนั้นได้ แต่ไม่รู้ว่าทำไมถึงไม่ทำ
ฉันเองก็เคยคิดแบบนั้นมาก่อน แต่พอรีแฟกเตอร์โปรเจกต์ให้เป็น JSDoc แล้วก็เปลี่ยนความคิด
ใน JSDoc ก็สามารถนิยาม generic slot ได้ด้วย
@templateตัวอย่าง:
ลิงก์ที่เกี่ยวข้อง
แพ็กเกจที่เขียนด้วย JSDoc มีประสบการณ์ใช้งานที่ดีเพราะ กด CMD/CTRL แล้วไปยังโค้ดจริงได้
เมื่อ 5 ปีก่อนในงาน meetup มีวิทยากรคนหนึ่งพูดว่า “ถ้าไม่ชอบ TypeScript, JSDoc ก็เป็นทางเลือกได้”
ฉันอธิบายไปว่าจริง ๆ แล้ว ทั้งคู่ก็คือ TypeScript เหมือนกัน แต่หัวหน้าของฉันไม่เชื่อ
JSDoc กับ TS ต่างก็เป็นการแสดง type แบบชัดเจนเหมือนกัน แต่ ไวยากรณ์ของ TS ทรงพลังยิ่งกว่า
ถึงอย่างนั้น สำหรับคนที่อยากคงสภาพแวดล้อม JS ไว้พร้อมได้ประโยชน์จากเครื่องมือด้าน type, JSDoc ก็เป็นทางเลือกที่ดี
อีกมุมหนึ่งคือ JSDoc ไม่ใช่ TypeScript
type ที่นิยามด้วย
@typedefจะถูก export อัตโนมัติ และไม่มีวิธีควบคุมเรื่องนี้ประเด็นที่เกี่ยวข้อง
เพราะเหตุนี้เวลาพัฒนาไลบรารี IntelliSense จึงแสดงผลรก ๆ ออกมา จนใช้งานลำบาก
แค่คัดลอกไฟล์ “my-component.js” ไปตรง ๆ ก็ทำงานได้โดยไม่ต้อง build
แต่ถ้าเป็นโปรเจกต์ใหญ่ ฉันชอบไวยากรณ์ TS มากกว่า
เห็นด้วยกับคำกล่าวที่ว่า “JSDoc ไม่ใช่ทางเลือกแทน TypeScript”
JSDoc ก็ ให้การวิเคราะห์แบบ static ได้ แต่ไม่มี build step
ดู เอกสารทางการของ Node
แต่ TypeScript ที่อิง JSDoc ใช้งานในเบราว์เซอร์ได้ด้วย
ส่วนตัวฉันชอบ ความอ่านง่ายของไวยากรณ์ TS และการลบ type ด้วยเครื่องมืออย่าง
swcก็เร็วพออยู่แล้วเหตุผลที่ TypeScript ชนะทางเลือกอื่นทั้งหมดได้ก็เพราะมัน ยังคงเป็น type checker ไม่ใช่ภาษาใหม่
ช่วงแรกทิศทางอาจแกว่งไปบ้าง แต่ก็ปรับได้เหมาะสม และตอนนี้ในโค้ดส่วนใหญ่แทบไม่ได้ใช้ enums กันแล้ว
ใน VSCode ถ้าเปิดการตั้งค่า “TypeScript: Prefer Go To Source Definition” ก็จะกระโดดไปยังซอร์สจริงได้
และถ้าเพิ่ม
declarationMap: trueในtsconfigก็จะทำงานได้แม่นยำขึ้นฉันแทบจะชอบให้ cmd+click แล้วเห็นซอร์ส อยู่เสมอ