5 คะแนน โดย GN⁺ 2024-05-05 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Figma ได้ย้ายโค้ด Skew ทั้งหมดไปเป็น TypeScript หลังจากภาษาภายในที่ใช้กับการเรนเดอร์บนมือถือและตัวดูต้นแบบชนข้อจำกัดด้านการออนบอร์ด การผสานรวม และระบบนิเวศ
  • เงื่อนไขที่ทำให้การย้ายเกิดขึ้นได้คือการรองรับ WebAssembly บนเบราว์เซอร์มือถือที่เพิ่มขึ้น การแทนที่เอนจินหลักด้วยเอนจิน C++ และการเติบโตของทีมโปรโตไทป์และมือถือ
  • การย้ายดำเนินไป 3 ระยะ: Write Skew, build SkewWrite Skew, build TypeScriptWrite TypeScript, build TypeScript โดยมี Skew-to-TypeScript transpiler ช่วยคงรูปแบบการพัฒนาเดิมไว้
  • ระหว่างการย้ายจริงพบปัญหาความเข้ากันได้ เช่น ประสิทธิภาพของ array destructuring, ความต่างของ devirtualization, ลำดับการ initialisation, การเชื่อม source map และการไม่มี conditional compilation
  • สุดท้ายสามารถได้โค้ดเบส TypeScript ที่ผ่าน unit test และมีประสิทธิภาพใกล้เคียง Skew โดย Figma มองว่าภารกิจถัดไปคือการรวมโค้ดภายในและภายนอก การจัดการแพ็กเกจ และการใช้ประโยชน์จาก ecosystem ของ TypeScript

เหตุผลที่ย้ายจาก Skew ไป TypeScript

  • Skew เริ่มจากโปรเจ็กต์ข้างเคียงในช่วงแรกของ Figma และตอบโจทย์การสร้างตัวดูต้นแบบที่รองรับทั้งเว็บและมือถือได้อย่างรวดเร็ว
  • แม้มันจะพัฒนาเป็นภาษาที่คอมไพล์เป็น JavaScript พร้อมให้ทั้งการปรับแต่งประสิทธิภาพขั้นสูงและเวลา compile ที่รวดเร็ว แต่เมื่อโค้ดในตัวดูต้นแบบสะสมมากขึ้น ต้นทุนการดูแลรักษาก็เพิ่มขึ้นตาม
    • พนักงานใหม่เรียนรู้ได้ยาก
    • ผสานเข้ากับโค้ดเบสส่วนอื่นได้ไม่ง่าย
    • ไม่มี ecosystem ของนักพัฒนา นอก Figma
  • สิ่งที่คาดหวังจากการย้ายไป TypeScript คือการเพิ่มความเร็วในการพัฒนาและขยายขอบเขตการทำงานร่วมกัน
    • ใช้ static import และการจัดการแพ็กเกจแบบเนทีฟเพื่อทำให้การรวมโค้ดภายในและภายนอกง่ายขึ้น
    • ใช้เครื่องมือจากชุมชนนักพัฒนาขนาดใหญ่ เช่น linter, bundler และ static analyzer
    • ใช้ความสามารถ JavaScript สมัยใหม่อย่าง async/await และระบบ type ที่ยืดหยุ่นกว่า
    • ลดอุปสรรคในการออนบอร์ดนักพัฒนาใหม่และการมีส่วนร่วมจากทีมอื่น

เงื่อนไขที่เพิ่งทำให้การย้ายเป็นไปได้ในช่วงหลัง

  • ตอนที่ Figma สร้างโค้ดเบสมือถือครั้งแรก เบราว์เซอร์มือถือยังไม่รองรับ WebAssembly และการโหลด bundle ขนาดใหญ่ให้มีประสิทธิภาพก็ยังทำได้ยาก
  • TypeScript เองก็ยังอยู่ในช่วงเริ่มต้น ทำให้ Skew ซึ่งมี static type และระบบ type ที่เข้มงวดกว่ากลายเป็นตัวเลือกที่ใช้งานได้จริงกว่าในเวลานั้น
  • ภายในปี 2018 WebAssembly ได้รับการรองรับอย่างกว้างขวางบนมือถือ และตามเกณฑ์การทดสอบของ Figma ประสิทธิภาพบนมือถือในปี 2020 ก็ไปถึงระดับที่เชื่อถือได้
  • จุดแข็งช่วงแรกของ Skew คือการปรับแต่งแบบคอมไพเลอร์ดั้งเดิม เช่น constant folding, devirtualization และการปรับแต่งที่เน้นเว็บโดยเฉพาะอย่างการสร้าง JavaScript ที่ใช้การคำนวณจำนวนเต็มจริง
  • benchmark ในปี 2020 ยืนยันว่าการโหลดต้นแบบของ Figma ด้วย TypeScript บน Safari อาจ ช้าลงเกือบ 2 เท่า และเพราะบน iOS อนุญาตให้ใช้ Safari เป็นเอนจินเบราว์เซอร์เดียว นี่จึงเป็นปัจจัยที่ขวางการย้าย
    • ใน iOS 17.4 Apple เปิดให้ผู้ใช้ในสหภาพยุโรปใช้เอนจินเบราว์เซอร์อื่นได้ แต่ผู้ใช้นอกภูมิภาคนั้นยังคงมี WebKit เป็นเอนจินเดียว
  • หลังจากนั้น องค์ประกอบหลักหลายส่วนของเอนจิน Skew ถูกแทนที่ด้วยองค์ประกอบฝั่งเอนจิน C++ ทำให้ความเสี่ยงด้านประสิทธิภาพจากการย้ายไป TypeScript ลดลง
    • รวมถึงเส้นทางโค้ดที่ร้อนที่สุดอย่างการโหลดไฟล์
    • ทำให้มั่นใจได้ว่าการย้ายไป TypeScript ยังรับภาระด้านประสิทธิภาพได้
  • เมื่อทีมโปรโตไทป์และมือถือเติบโตเป็นองค์กรที่ใหญ่ขึ้น ก็สามารถทุ่มทรัพยากรให้กับการย้ายแบบอัตโนมัติและการปรับปรุง developer experience ได้

การย้ายโค้ดเบส 3 ระยะ

  • ในต้นแบบการย้ายครั้งแรกเมื่อปี 2020 ประสิทธิภาพของ TypeScript ช้ากว่าเกือบ 2 เท่า
  • หลังจากการรองรับ WebAssembly และการย้ายเอนจินมือถือไป C++ มีความพร้อมเพียงพอ Figma ได้ใช้ช่วง Maker Week ปรับต้นแบบเดิมจนสาธิตการย้ายที่ทำงานได้จริงและผ่านการทดสอบทั้งหมด
  • เป้าหมายคือเปลี่ยนทั้งโค้ดเบสเป็น TypeScript แต่การเขียนใหม่ด้วยมืออาจทำให้ความเร็วในการพัฒนาลดลง และเสี่ยงต่อ runtime error กับประสิทธิภาพที่ตกลง
  • ความหมายเชิงภาษาของ Skew และ TypeScript ต่างกันมากกว่าการย้ายระหว่างภาษาในตระกูล “JavaScript แบบมี type” ทั่วไป
    • TypeScript จะ initialisation namespace และ class ก็ต่อเมื่อมีการ import ไฟล์นั้นแล้ว
    • ถ้าลำดับ import ไม่ตรงกับที่คาด อาจเกิด runtime error ได้
    • Skew ทำให้ทุกสัญลักษณ์ในโค้ดเบสพร้อมใช้งานจาก runtime เมื่อโหลดเสร็จ
  • Figma พัฒนา Skew-to-TypeScript transpiler ขึ้นจากงานที่ Evan Wallace เคยเริ่มไว้ก่อนหน้า
  • Phase 1: เขียน Skew และ build Skew

    • พัฒนา transpiler โดยยังคงกระบวนการ build เดิมไว้
    • commit โค้ด TypeScript ที่ถูก generate ลง GitHub เพื่อให้นักพัฒนาเห็นรูปแบบของโค้ดเบสใหม่
  • Phase 2: เขียน Skew และ build TypeScript

    • หลังจากสร้าง bundle TypeScript ที่ผ่าน unit test ทั้งหมดได้แล้ว ก็ทยอยปล่อย production traffic ไปยัง build จากโค้ดเบส TypeScript แบบค่อยเป็นค่อยไป
    • นักพัฒนายังคงเขียน Skew และ transpiler จะแปลงโค้ด Skew เป็น TypeScript เพื่ออัปเดตโค้ด TypeScript บน GitHub
    • มีการแก้ type error ในโค้ดที่ generate อย่างต่อเนื่อง
    • TypeScript สามารถสร้าง bundle ที่ใช้งานได้แม้ยังมี type error
  • Phase 3: เขียน TypeScript และ build TypeScript

    • หลังจากผู้ใช้ทั้งหมดใช้งานผ่านกระบวนการ build แบบ TypeScript แล้ว ก็เปลี่ยนให้โค้ด TypeScript เป็นแหล่งความจริงหนึ่งเดียวของการพัฒนา
    • เลือกช่วงเวลาที่ไม่มีการ merge โค้ดเพื่อหยุดกระบวนการ generate อัตโนมัติ และลบโค้ด Skew ออกจากโค้ดเบส
    • ในคืนวันศุกร์มีการ merge การลบระบบ generate อัตโนมัติ และปรับงาน CI ให้รันไฟล์ TypeScript โดยตรง
    • ระหว่างทยอยปล่อย พบภายในว่าฟีเจอร์ Smart Animate พัง แต่ด้วยการปล่อยแบบมี gate จึงสามารถปิด rollout แก้ไข และทบทวนแผนได้

ปัญหาที่เผยออกมาจาก transpiler

  • โดยทั่วไปคอมไพเลอร์ประกอบด้วย frontend และ backend
    • frontend จะ parse โค้ดอินพุต ตรวจ type และไวยากรณ์ ก่อนแปลงเป็น intermediate representation (IR)
    • backend จะแปลง IR ไปเป็นภาษาอื่น
    • backend ของคอมไพเลอร์ Skew สร้าง JavaScript ที่ผ่านการทำให้อ่านยากและย่อขนาดแล้ว
  • transpiler คือคอมไพเลอร์ชนิดพิเศษที่ backend สร้างโค้ดที่มนุษย์อ่านได้ และ Figma จำเป็นต้องสร้าง TypeScript ที่มนุษย์อ่านได้จาก Skew IR
  • ช่วงแรกการพัฒนาค่อนข้างราบรื่นเพราะได้แรงบันดาลใจจาก JavaScript backend ของ Skew มาก แต่ในช่วงท้ายก็มีปัญหาหลายอย่างที่ติดตามและจัดการได้ยาก
  • ประสิทธิภาพของ array destructuring

    • ระหว่างตรวจสอบความต่างของประสิทธิภาพแบบออฟไลน์ระหว่าง Skew กับ TypeScript ในต้นแบบตัวอย่าง พบว่าเฟรมเรตของ TypeScript ต่ำกว่า
    • สาเหตุมาจาก array destructuring ของ JavaScript
    • ในคำสั่งอย่าง const [a, b] = function_that_returns_an_array() นั้น JavaScript จะไม่ได้ index เข้าถึง array โดยตรง แต่จะสร้าง iterator แล้ววนอ่านแทน
    • Figma ใช้วิธีนี้เพื่อดึงอาร์กิวเมนต์จากคีย์เวิร์ด arguments ของ JavaScript และในบาง test case ทำให้ประสิทธิภาพช้าลง
    • เมื่อเปลี่ยนจาก destructuring มาเป็นการ generate โค้ดที่ index เข้า array ของ arguments โดยตรง ก็ช่วยลด latency ต่อเฟรมได้สูงสุด 25%
  • การปรับแต่ง devirtualization ของ Skew

    • คอมไพเลอร์ Skew จะทำการปรับแต่ง devirtualization โดยย้ายฟังก์ชันใน class ออกไปเป็นฟังก์ชัน global ภายใต้เงื่อนไขบางอย่าง
    • myObject.myFunc(a, b) อาจกลายเป็น myFunc(myObject, a, b)
    • TypeScript ไม่ทำการปรับแต่งนี้
    • ความเสียหายของ Smart Animate เกิดขึ้นเมื่อ myObject เป็น null
    • การเรียกแบบ devirtualized ทำงานได้ตามปกติ แต่การเรียกแบบ non-devirtualized ทำให้เกิดข้อยกเว้นจากการเข้าถึง null
    • Figma เพิ่ม logging ให้กับทุกฟังก์ชันที่อาจเข้าร่วม devirtualization เพื่อค้นหาจุดเรียกที่มีปัญหาแบบเดียวกัน
    • หลังเปิด logging ชั่วคราวและวิเคราะห์ production log ก็แก้จุดเรียกที่มีปัญหาได้
  • ความต่างของลำดับการ initialisation

    • ใน Skew จะประกาศตัวแปร, class, namespace และฟังก์ชันไว้ตรงไหนของโค้ดก็ได้โดยไม่ต้องสนใจลำดับการประกาศ
    • แต่ใน TypeScript ลำดับการ initialisation ของตัวแปร global หรือการประกาศ class มีความสำคัญ
    • ถ้า initialisation static class variable ก่อนนิยาม class จะเกิด compile-time error
    • transpiler รุ่นแรกไม่ได้ใช้ namespace และทำให้ทุกฟังก์ชันถูก flatten ลงสู่ global scope เพื่อคงพฤติกรรมคล้าย Skew
    • โค้ดที่ได้อ่านยาก และต่อมาจึงแก้ transpiler ให้พิมพ์โค้ด TypeScript ตามลำดับที่ถูกต้อง พร้อมนำ namespace กลับมาเพื่อให้อ่านง่ายขึ้น
    • สุดท้ายสามารถสร้าง transpiler ที่ compile โค้ด TypeScript ซึ่งผ่าน unit test และมีประสิทธิภาพเทียบกับ Skew ได้
    • ปัญหาเล็กน้อยบางอย่างไม่ได้แก้ใน transpiler ใหม่ แต่แก้ด้วยการปรับโค้ดต้นฉบับ Skew ด้วยมือ หรือค่อยแก้หลังย้ายไป TypeScript แล้ว

รักษาประสบการณ์การดีบักด้วย source map

  • Figma ให้ความสำคัญกับ source map มาก เพื่อให้นักพัฒนายังดีบักได้ต่อเนื่องระหว่างการย้ายไป TypeScript
  • debugger ของเบราว์เซอร์เข้าใจเฉพาะ JavaScript แต่นักพัฒนาจะวาง breakpoint บนซอร์ส Skew หรือ TypeScript
  • source map ทำหน้าที่เชื่อมตำแหน่งใน JavaScript ที่คอมไพล์แล้วเข้ากับตำแหน่งในซอร์สดั้งเดิม
    • ตัวอย่างเช่นสามารถแมป helper → c, myInt → a, arrayOfInts → b
  • โดยทั่วไป source map ไฟล์เดียวที่มีนามสกุล .map จะถูกเชื่อมกับ JavaScript bundle สุดท้าย
  • โครงสร้างเดิมสามารถสร้าง source map จาก Skew → JavaScript ได้ แต่ใน Phase 2 pipeline เปลี่ยนเป็น Skew → TypeScript → esbuild bundling
  • ถ้าใช้ source map เดิมต่อไป การแมประหว่าง JavaScript กับโค้ด Skew จะเพี้ยนและนักพัฒนาจะดีบักไม่ได้
  • กระบวนการ build ใหม่สร้าง source map เป็น 3 ขั้น
    • Step 1: esbuild สร้าง source map ts-to-js.map สำหรับ TypeScript → JavaScript
    • Step 2: transpiler สร้าง source map Skew → TypeScript สำหรับแต่ละไฟล์ Skew
    • Step 3: นำ source map ทั้งสองมาประกอบกันเป็น source map สุดท้ายแบบ Skew → JavaScript
  • source map สุดท้ายทำให้สามารถแมป JavaScript bundle ใหม่กลับไปยังโค้ด Skew ได้ จึงยังคง developer experience ใน Phase 2 ไว้ได้

การจัดการ conditional compilation ใน TypeScript

  • Skew รองรับ conditional compilation ผ่าน if ระดับบนสุดและตัวเลือก defines ที่ส่งให้คอมไพเลอร์
  • ความสามารถนี้ทำให้สร้าง bundle ที่ต่างกันจากโค้ดเบสเดียวกันได้
    • bundle จริงที่ส่งให้ผู้ใช้
    • bundle สำหรับ unit test โดยเฉพาะ
    • ฟังก์ชันหรือ class ที่ใช้ implementation ต่างกันใน build แบบ debug หรือ release
  • เนื่องจาก TypeScript ไม่มี conditional compilation Figma จึงใช้ defines ของ esbuild และความสามารถ dead code elimination หลังการตรวจ type เพื่อทำ conditional compilation ในขั้น bundling
  • วิธีนี้ทำให้ defines ไม่มีผลต่อการตรวจ type
    • โค้ดอย่าง testOnlyFunction ที่มีอยู่เฉพาะตอน BUILD == "TEST" จึงไม่สามารถคงอยู่ใน TypeScript แบบเดิมได้
  • วิธีแก้คือกำหนด class และ method ให้มีอยู่เสมอ แล้วค่อยแยกเงื่อนไขภายใน method ตามค่า BUILD
    • หากมีการเรียก testOnlyFunction ใน build ที่ไม่ใช่สำหรับการทดสอบ ก็จะ throw error ว่า Unexpected call to test-only function
  • JavaScript สุดท้ายยังสามารถถูก compile ให้ออกมาเหมือนกับ JavaScript ที่โค้ด Skew เดิมสร้างได้
  • อย่างไรก็ตาม สัญลักษณ์บางตัวที่เดิมมีเฉพาะในบางโหมดการ compile จะกลายเป็นมีอยู่ในทุกโหมดหลังการเปลี่ยนแปลง ทำให้ bundle สุดท้ายใหญ่ขึ้นเล็กน้อย
  • จากการทดสอบ การเพิ่มขึ้นของขนาด bundle อยู่ในระดับยอมรับได้ และสัญลักษณ์ระดับบนสุดที่ไม่ได้ export สามารถถูกลบออกด้วย tree-shaking

ทิศทางหลังการย้าย

  • การย้ายโค้ด Skew ทั้งหมดไป TypeScript ทำให้หนึ่งในโค้ดเบสหลักของ Figma ถูกปรับให้ทันสมัยขึ้น
  • การผสานกับโค้ดภายในและภายนอกทำได้ง่ายขึ้น และนักพัฒนาทำงานได้มีประสิทธิภาพมากขึ้น
  • ภายใต้ความต้องการและศักยภาพของ Figma ในตอนนั้น การเริ่มต้นโค้ดเบสด้วย Skew ถือเป็นการตัดสินใจที่เหมาะสม
  • เมื่อเทคโนโลยีเติบโตจนพร้อม ตัวเลือกอย่าง TypeScript ที่ครั้งหนึ่งยังไม่เหมาะสมก็กลายเป็นตัวเลือกที่เหมาะในปัจจุบัน
  • งานต่อจากนี้ที่ Figma กำลังสำรวจมีดังนี้
    • การผสานเข้ากับโค้ดเบสส่วนที่เหลือ
    • การจัดการแพ็กเกจที่ง่ายขึ้นมาก
    • การใช้ฟีเจอร์ใหม่จาก ecosystem ที่คึกคักของ TypeScript ได้โดยตรง
  • ในกระบวนการย้าย ทีมได้เรียนรู้หลายแง่มุมของ TypeScript เช่นการ resolve import, ระบบโมดูล และการสร้างโค้ด JavaScript

1 ความคิดเห็น

 
GN⁺ 2024-05-05
ความคิดเห็นจาก Hacker News
  • น่าทึ่งที่ Figma เคยมี ภาษาคัสตอม สำหรับ JS เป็นของตัวเอง และยิ่งน่าทึ่งกว่าที่มันเร็วกว่า TS
    แต่ก็น่าสนใจเหมือนกันที่ท้ายที่สุดกลับย้ายไปใช้ TS ที่ช้ากว่า
    เรื่องแบบนี้เห็นได้ค่อนข้างบ่อย คือบริษัทสร้างเทคโนโลยีเองในช่วงแรก และเมื่อเติบโตขึ้นก็ย้ายไปใช้สิ่งที่ “เป็นมาตรฐาน” มากกว่า

    • ดูเป็นเส้นทางที่ค่อนข้างคลาสสิก ช่วงแรกมีบุคคลฝีมือเยี่ยมอย่าง Evan ที่วางทั้ง toolchain และแกนหลักของผลิตภัณฑ์ไว้ทั้งหมด แล้วต่อมาก็จากไป ถูกกันออกไป หรือหมดความสนใจ
      เมื่อทีมและผลิตภัณฑ์ใหญ่ขึ้นมาก ก็จะย้ายแพลตฟอร์มกลับไปสู่สแต็กที่คุ้นเคยและใช้กันแพร่หลายกว่า
      ความสำเร็จของการเปลี่ยนผ่านแบบนี้ขึ้นอยู่มากกับความแข็งแกร่งของวัฒนธรรมวิศวกรรมในองค์กร ไม่มีหลักฐานชัดเจน แต่ดูเหมือน Evan และผู้ก่อตั้งจะสร้างวัฒนธรรมวิศวกรรมที่ยอดเยี่ยมไว้ให้ Figma และดูมีความยืดหยุ่นพอที่จะฟื้นตัวและแก้ไขได้แม้จะเกิดความผิดพลาด
    • ถ้าอ่านละเอียดจะเห็นว่าส่วนที่ ไวต่อประสิทธิภาพ ถูกย้ายไปเป็น C++/WASM หมดแล้ว
      ณ จุดนั้นก็เหลือแค่โค้ดเชื่อมต่อ และไม่มีเหตุผลให้ภาษาคัสตอมยังคงอยู่ต่อไป
    • ในชุมชน PHP ก็เคยมีเรื่องคล้ายกัน Facebook เป็นตัวอย่างเด่น แต่ Yahoo ก็มีการปรับแต่งภายในพอสมควร และบริษัทอื่น ๆ ก็เคยปรับของตัวเองเพื่อประสิทธิภาพหรือความปลอดภัยเช่นกัน
      แล้วพอถึงจุดที่ชุมชนตามทันและมีแรงขับมากกว่าบริษัทเอง การย้ายกลับไปใช้ implementation มาตรฐานก็กลายเป็นทางเลือกที่สมเหตุสมผล
      ผมมองว่ากระแสจาก Borg ของ Google ไปสู่ k8s ก็คล้ายกันอยู่บ้าง เพียงแต่ Google เลือกเชิญชวนชุมชนเข้ามารอบมาตรฐานที่ตัวเองสร้างขึ้น
    • การที่เร็วกกว่า TS ในงานเฉพาะทางแคบ ๆ นั้นเป็นเกณฑ์ที่ค่อนข้างต่ำ แต่เมื่อระบบขยายใหญ่ขึ้น เกณฑ์นี้ต้องพิจารณาควบคู่กับ pool ของนักพัฒนา ที่หาได้ในตลาดด้วย
      ต้องเทียบต้นทุนของนักพัฒนาที่ต้องมาเรียนรู้เฟรมเวิร์กใหม่ทั้งหมดตั้งแต่แรก กับคุณค่าของนักพัฒนาที่มีประสบการณ์ซึ่งมีเอกสารรองรับอยู่แล้วบนแพลตฟอร์มหรือเทคโนโลยีที่ใช้อยู่
      ถ้าพัฒนาเองทั้งหมด คุณอาจรีดประสิทธิภาพมหาศาลออกมาได้ไม่ว่าจะเป็นภาษาไหนหรือเฟรมเวิร์กไหน แต่ยิ่งโปรเจกต์ต้องรองรับสถานการณ์มากขึ้น ก็ยิ่งต้องใช้จำนวนนักพัฒนาเพื่อดูแลรักษามากขึ้น และสุดท้ายจะมีจุดหนึ่งที่ ซอฟต์แวร์สำเร็จรูป ดีกว่า แม้จะช้ากว่าและไม่ตรงความต้องการเป๊ะก็ตาม
      ในระยะยาว ถ้ายังรักษามาตรฐานการจ้างงานระดับยูนิคอร์นได้ตลอด ก็อาจคงสแต็กแบบคัสตอมทั้งหมดไว้ได้ แต่ส่วนใหญ่องค์กรจำเป็นต้องย้ายไปสู่สิ่งที่พนักงานใหม่ระดับทั่วไปจะเข้ามาดูแลต่อได้ ซึ่งโดยมากก็คือผลิตภัณฑ์จากผู้ขายซอฟต์แวร์ที่มีชื่อเสียงและออกจะน่าเบื่อหน่อย
    • ของแปลกเฉพาะทางอย่างภาษาคัสตอม ถ้าคุณไม่ใช่คนสร้างแต่เป็นแค่ผู้ใช้ เช่นเป็น นักพัฒนา ที่ต้องใช้ภาษานั้น มันถือว่า เสียเปรียบในเรซูเม่
      สำหรับคนที่มีความทะเยอทะยาน มันอาจกลายเป็นเหตุผลให้ลาออก ทำให้การจ้างยากขึ้น และการ onboarding ก็ยาวนานขึ้นด้วย พอถึงวันที่ต้องเลิกใช้ภาษาคัสตอม ก็ยังต้องทำโปรเจกต์เปลี่ยนผ่านขนาดใหญ่อีก
      ถ้ามันให้ประสิทธิภาพเพิ่มขึ้นมหาศาล หรือช่วยให้เขียนโค้ดที่ปลอดภัยกว่าได้มาก หรือคุณตั้งใจจะจ้างเฉพาะคนที่ชอบเรียนรู้ภาษาใหม่จริง ๆ บางครั้งมันก็อาจคุ้มค่า
  • ผมเคยทำงานในโปรเจกต์นี้ และเขียนเพิ่มไว้ใน Twitter นิดหน่อย: https://twitter.com/andrew_k_chan/status/1786769203912925477
    ชื่อบทความค่อนข้างชวนให้เข้าใจผิด ที่ Figma ใช้ TypeScript กับส่วนอื่นของ codebase มาเกือบ 10 ปีแล้ว และเกือบตลอดช่วงนั้นก็มีโค้ด TypeScript มากกว่า Skew
    ตามที่อธิบายในบล็อกโพสต์ Skew ถูกใช้กับ mobile engine, ต่อมาคือ prototyping player, ฟีเจอร์ mirroring และพื้นที่ผลิตภัณฑ์อีกหนึ่งหรือสองส่วนถ้าจำไม่ผิด

  • Skew ไม่ได้เร็วกว่า TypeScript แค่นิดหน่อย
    ตามคำกล่าวของ Evan Wallace อดีต CTO ของ Figma มัน เร็วกว่า 1.5 ถึง 2 เท่า เพราะการทำ optimization ที่เป็นไปได้จากระบบ type ที่เข้มงวดกว่า

    • น่าเสียดายที่เบราว์เซอร์ไม่ได้พัฒนาโหมด "use strong" สำหรับ JS ต่อ
      ดูเหมือนจะมีโจทย์ยากขนาดใหญ่พอสมควร แต่การลดความ dynamic ลงบางส่วนเพื่อให้ optimization คาดเดาได้มากขึ้นก็ดูเป็นการแลกเปลี่ยนที่ดีสำหรับแอประดับ production
    • คำพูดที่ว่า “เร็วกว่า 1.5 ถึง 2 เท่าเพราะระบบ type เข้มงวดกว่า” อาจไม่ได้ตรงไปตรงมาขนาดนั้น
      ถ้าปรับแต่ง hot path ให้ดี ข้อได้เปรียบระดับ 2 เท่า ก็น่าจะหายไปได้มาก
      แน่นอน คุณอาจบอกว่าถ้าอย่างนั้นก็ต้องบังคับให้เขียน JS แบบ optimize แล้วในหลายจุด ซึ่งกระทบต่อความอ่านง่าย เรื่องนั้นก็จริง แต่ทางเลือกในตอนนั้นคือใช้ภาษาใหม่ทั้งหมดที่คอมไพล์เป็น JS ซึ่งมีเครื่องมือรองรับและการยอมรับต่ำมาก
      มองย้อนกลับไปตอนนี้ก็ดูเหมือนจะไม่คุ้มขนาดนั้น และบล็อกโพสต์เองก็ให้ความรู้สึกเหมือนพยายามนำเสนอการตัดสินใจทางเทคนิคในอดีตที่เคยเป็นประเด็นถกเถียงให้ออกมาดูดีขึ้นเล็กน้อย
    • ทั้งบทความนี้ สิ่งที่น่าตกใจที่สุดคือ ความเร็วต่างกัน 2 เท่า
      เลยสงสัยว่ามีทางจำกัดการใช้ TypeScript ให้อยู่ใน subset ที่ให้ประสิทธิภาพระดับนั้นได้หรือไม่
  • น่าสนใจที่เวลาเขียนอะไรอย่าง const [a, b] = function_that_returns_an_array() นั้น JavaScript จะสร้าง iterator ที่วนผ่านอาร์เรย์ แทนที่จะเข้าถึงดัชนีจากอาร์เรย์โดยตรง
    ทำไม JS ถึงไม่ใช้การเข้าถึงดัชนีของอาร์เรย์ตรง ๆ ตอนทำ destructuring assignment ล่ะ?

    • อ็อบเจ็กต์อะไรก็ได้สามารถกลายเป็น iterable object ได้ถ้าเพิ่ม Symbol.iterator เข้าไป และ destructuring ก็ต้องทำงานกับอ็อบเจ็กต์แบบนั้นได้ด้วย แม้แต่ Symbol.iterator ของอาร์เรย์เองก็ยังถูกแพตช์ได้ ดังนั้น VM จึงต้องรองรับกรณีนี้
      Array.prototype[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }
      [...[4, 5, 6]]
      [1, 2, 3]
      ประสิทธิภาพอันย่ำแย่ของ iterator protocol ถูกถกเถียงกันตั้งแต่ตอนนั้นแล้ว แต่ก็ถูกมองข้ามไปด้วยเหตุผลว่า escape analysis จะเข้ามาแก้เอง [0] ผ่านมาเกือบ 10 ปีแล้ว ทุกวันนี้ escape analysis ก็ยังแก้ไม่ได้ ใช้ GC หนักมากและยังแย่อยู่ดี เป็นสเปกที่ออกแบบมาไม่ดีโดยคนที่ไม่ใส่ใจเรื่องประสิทธิภาพ
      ถ้าผู้ใช้ไม่ได้แพตช์ Symbol.iterator เอาไว้ ก็อาจมีเหตุผลที่เอนจินจะทำ specialization ให้กับ array destructuring และ splicing เพื่อตัด overhead ของ iterator protocol ออกไป แต่ก็จะกลายเป็นปัญหาปวดหัวอีกแบบหนึ่ง
      [0] https://esdiscuss.org/topic/performance-of-iterator-next-as-...
    • ดูเหมือนจะเป็นเพราะมีของอย่าง proxy อยู่ด้วย
      ที่แปลกกว่านั้นคือ ใน JavaScript engine บางตัว การ destructure อาร์เรย์ที่มีสมาชิกแค่สองหรือสามตัวด้วย object destructuring แบบ {0: foo, 1: bar} อาจเร็วกว่า
  • แทบไม่ได้พูดถึง ผลกระทบต่อประสบการณ์นักพัฒนา ที่ต่อเนื่องจากการสูญเสียฟีเจอร์อำนวยความสะดวกของ Skew ไปเลย พูดแต่เรื่องการแปลงที่ทำครั้งเดียวตอนย้ายโค้ดเบส
    ตัวอย่างเช่น ใน TypeScript มักเกิดกรณีที่ต้อง import ไฟล์ตามลำดับที่ถูกต้องเท่านั้น ไม่อย่างนั้นอาจพังได้ อีกทั้ง destructuring ก็ช้า ดังนั้นถ้าประสิทธิภาพสำคัญแม้เพียงเล็กน้อยก็ไม่ควรใช้
    พอใช้ TypeScript มาหลายปี ก็พบว่ามีกับดักแบบนี้อยู่หลายสิบอย่าง บางอย่างรับทอดมาจาก JavaScript และบางอย่างก็ไม่ใช่ ถ้ามีวิศวกรจำนวนมาก อย่างน้อยก็น่าจะต้องมี style guide ขนาดมหาศาล
    เลยสงสัยว่ามีวิศวกรที่เสียดายการหายไปของ Skew บ้างไหม

    • การทำให้นักพัฒนาพอใจกับผลลัพธ์นั้นสำคัญมากอย่างแน่นอน ดังนั้นในขั้น rollout จึงมีช่วงที่นักพัฒนายังเขียน Skew ต่อไปได้ แต่การเปลี่ยนแปลงจะถูกเช็กอินเป็น TS ที่สร้างขึ้นโดยอัตโนมัติ
      แบบนี้ทำให้นักพัฒนาตรวจดูได้ใน PR review ว่าผลลัพธ์จะออกมาเป็นอย่างไร และสามารถรายงานปัญหาได้
      ในแง่ประสิทธิภาพและความถูกต้องของรันไทม์ เป็นความจริงที่ TS มีจุดที่ต้องระวังอยู่บ้าง ปัญหาอย่าง array destructuring ถูกจับได้ด้วย instrumentation และการมอนิเตอร์อย่างเข้มงวด
      ฟีเจอร์บางอย่างของ Skew ที่หายไป เช่น operator overloading และชนิดจำนวนเต็ม ก็เป็นเรื่องที่น่าเสียดายจริง ๆ แต่ท้ายที่สุดการย้ายครั้งนี้เป็นการตัดสินใจของทั้งทีม และผมเองก็มองว่าเป็นการตัดสินใจที่ถูกต้อง
  • เขียนไว้ว่า “ฟีเจอร์ JavaScript สมัยใหม่อย่าง async/await และ type system ที่ยืดหยุ่นกว่า” งั้นแปลว่าใน Skew มีแค่ callback เหรอ?

    • อาจจะมี Promise อยู่แล้วก็ได้?
  • สำหรับคนที่ไม่ได้รู้จัก Figma มากนัก ก็สงสัยว่าทำไมถึงใช้ WebAssembly

    • น่าจะพอรู้ว่า Figma เป็นซอฟต์แวร์ออกแบบ UX ซึ่งโดยพื้นฐานแล้วก็คือโปรแกรมกราฟิก วาดรูปทรง เลื่อนหน้าจอ ซูมเข้าออก
      Figma ทำสิ่งเหล่านี้ได้ดีมาก แม้จะอยู่บนแคนวาสขนาดใหญ่มากที่มีหน้าจอ UI ซับซ้อนจำนวนมาก ก็ยังลื่นจนน่าเหลือเชื่อ
      ทุกวันนี้ต่อให้เป็นเดสก์ท็อปแอป ก็ยังแทบไม่มีตัวไหนทำได้ดีขนาดนี้ ผมมั่นใจว่านี่คือส่วนสำคัญของความสำเร็จของ Figma ในด้าน optimization
    • งานแก้ไขเวกเตอร์มีงานจำนวนมากที่ ติดคอขวดที่ CPU Evan เคยเขียนโพสต์เกี่ยวกับการเปลี่ยนมาใช้ WASM ไว้: https://medium.com/figma-design/webassembly-cut-figmas-load-...
    • เป้าหมายแรกเริ่มคือการเป็น Photoshop ในเบราว์เซอร์
  • รายละเอียดที่น่าสนใจเกี่ยวกับวิธีที่ Figma สร้าง TypeScript DSL + compiler แบบกำหนดเองเพื่อแก้ปัญหาด้านความปลอดภัยหรือเรื่องสิทธิ์ มีอยู่ในบล็อกโพสต์อีกชิ้นด้วย
    https://www.figma.com/blog/how-we-rolled-out-our-own-permiss...

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

    • Skew เป็นโอเพนซอร์สอยู่แล้ว แต่ไม่ได้รับการบำรุงรักษาอีกต่อไป: https://github.com/evanw/skew
    • ผมไม่ค่อยชอบมุมมองแบบนี้เท่าไร ซึ่งมักมาจาก การขาดประสบการณ์กับโอเพนซอร์ส
      การเป็นโอเพนซอร์สไม่ได้แปลว่าจะมีคนมาช่วยฟรี ๆ สำหรับ PR ที่ไม่ใช่เรื่องเล็กน้อยทุกอัน มักต้องตามมาด้วยการรีวิวยาว ๆ การถกเถียง และอาจถึงขั้นต้องเขียนใหม่
  • แม้จะชอบ TypeScript และมีระบบฟูลสแตกที่ใช้ TypeScript ด้วย แต่ก็ไม่ได้สมบูรณ์แบบ การตั้งค่า TypeScript ในโมโนรีโปเป็นฝันร้าย
    ถ้าจะทำให้มันเข้าใจแพ็กเกจภายในภายใต้ pnpm โมโนรีโปได้ ก็ต้องมานั่งปรับแต่ง tsconfig.json ด้วยมือจำนวนมาก เพื่อให้ทุกพาธสอดคล้องกัน
    และก่อนที่จะมีแพ็กเกจ tsx อันยอดเยี่ยม โซ่เครื่องมือสำหรับโปรดักชันนั้นแทบจะดูแลรักษาไม่ได้เลย
    แถมยังช้าแบบเกินเหตุอีกด้วย มีปัญหาที่ Zod ทำให้ประสิทธิภาพของ TypeScript language server ช้าลงอย่างมาก จนสุดท้ายต้องนำ project references มาใช้และปิด project reference redirection
    โดยรวมแล้ว TypeScript ยังมีพื้นที่ให้ปรับปรุงอีกมาก โดยเฉพาะในด้านโมโนรีโปและประสิทธิภาพ

  • ต้องคำนึงด้วยว่า Zod เป็นกรณีที่ค่อนข้าง ผิดปกติ ในแง่ว่ามันทำให้ความเร็วในการตรวจสอบชนิดช้าลงได้มากแค่ไหน ตัวอย่างเช่นที่ https://dev.to/nicklucas/typescript-runtime-validators-and-d... มีการทดสอบหนึ่งที่เกือบแตะ 300ms แต่ไลบรารีอื่น ๆ แทบไม่แตะ 100ms เลย

  • เป็นที่รู้กันว่า Zod ช้า เช่น Typebox เร็วกว่าเยอะ ไม่ได้หมายความว่าควรเปลี่ยน เพียงแต่หมายความว่านี่ไม่ใช่ปัญหาของ TypeScript หรือไม่ใช่ปัญหาที่ TypeScript ต้องเป็นฝ่ายแก้
    ส่วนปัญหาโมโนรีโป ฟังดูเหมือนว่าไม่ได้ใช้การตั้งค่าเดียวกันกับทุกแพ็กเกจ ถ้าเป็นแบบนั้น ผมจะไปแก้ที่การบังคับใช้การตั้งค่าและมาตรฐานการเขียนโค้ดเดียวกันก่อน นี่ก็ไม่ใช่ปัญหาของ TypeScript เช่นกัน
    หรือก่อน tsx ใช้ ts-node อยู่หรือเปล่า? ถ้าใช่ tsx แข็งแรงกว่ามาก มันใช้งานได้ดีเลย

  • ไม่ใช่ว่า TypeScript ต้องรองรับชุดการเลือกเครื่องมือและไลบรารีแบบใดแบบหนึ่ง แต่ตรงกันข้าม ฝั่งเครื่องมือและไลบรารีต่างหากที่ต้องปรับตัว
    ผมดูแลโมโนรีโปขนาดกลาง มีหลายแอปและหลายบริการที่ส่วนใหญ่ใช้ TypeScript และมันก็ทำงานได้ดีพอสมควรด้วยการตั้งค่า npm workspaces แบบธรรมดา ๆ

  • การบอกว่า TypeScript ช้าเพราะ Zod ช้า ก็คล้ายกับการบอกว่า C++ ช้าเพราะ JavaScript ช้า
    ไม่ได้หมายความว่า TypeScript เร็วเลยแม้แต่น้อย แต่เพราะมันเขียนด้วย JavaScript จึงยากที่จะเร็ว
    อย่างไรก็ตาม การปล่อยให้ TypeScript ต้องรันโค้ดเพื่อทำ type inference ในแอปพลิเคชันขนาดใหญ่ ก็ดูเหมือนเป็น ปัญหาที่สร้างขึ้นเอง