แนวทางการจัดการ version ของ npm (Node.js)
เชื่อว่านักพัฒนาสายเว็บส่วนใหญ่แทบไม่มีใครไม่รู้จัก Node.js ภาษานี้มีพื้นฐานมาจากภาษา JavaScript อีกทีหนึ่ง การเรียนรู้เมื่อรู้แล้วสามารถนำไปใช้ได้หลากหลายแพลตฟอร์มมาก ๆ
เคยสงสัยกันไหมว่า node.js จัดการเรื่อง version ยังไง ? version นี่หมายถึง version ของโปรแกรม หรือ library ที่เราติดตั้ง แล้วปรากฎอยู่ใน package.json file เช่น "react": "17.0.1"
แล้ว 17.0.1 มันจัดการยังไงหรอ ? ก็เลยปูเข้าสู่เนื้อหาของเรา เพราะตัวที่จัดการ version ของ application หรือ library ที่เรา ๆ ติดตั้งกันใน node.js คือ node-semver
What is Semantic Versioning (semver) ?
semver เป็นวิธีการระบุการเปลี่ยนแปลงของซอฟต์แวร์วิธีการหนึ่ง ซึ่งได้รับความนิยมนำมาใช้ กัน เราก็อาจเห็นได้จากหลากหลายโปรแกรม เช่น iOS ก็ยังใช้ เห็นได้จากที่มีเลข version ใหม่ ๆ ทุกปี 12.0.0, 13.0.0 และล่าสุด 14.x.x
Semver Construction
semver-compatibile version ถูกสร้างชุดตัวเลข 3 ชุด ที่คั่นด้วยเครื่องหมายมหัพภาค (.) ชุดตัวเลข 3 ชุดที่ว่า มีชื่อเรียกว่า major minor และ patch ตามลำดับ
ตัวเลขแต่ละชุดจะถูกรันไปเรื่อย ๆ ขึ้นอยู่กับว่า ตัวเลขชุดนั้นแสดงถึงความหมายอะไร
ชุดของตัวเลข MAJOR.MINOR.PATCH จะเพิ่มขึ้นเมื่อ..
- MAJOR vesion เมื่อเราจะเปลี่ยนแปลงซอฟต์แวร์ของเราครั้งใหญ่ ที่อาจส่งผลวิธีการเรียกใช้ซอฟต์แวร์นั้นต่างออกไป หรือไม่ซัพพอร์ตบางอุปกรณ์ หรือ OS เช่น iOS 14 จะไม่รองรับ iPhone 6s หรือการที่แอปธนาคารไม่รองรับ Android OS version ใหม่ ๆ บางที่ major update จะเป็นการเปลี่ยนแปลงครั้งใหญ่ในรอบปี
- MINOR verion เมื่อเราต้องการเพิ่ม functionality เพิ่มหลังจากออก major update ไปแล้ว แต่ยังอยากเพิ่มความสามารถใหม่ ๆ ใส่เข้าไปด้วย เช่น iOS 14.5.x ที่รองรับการปลดล็อค iPhone ด้วย Apple Watch
- PATCH version เมื่อเราต้องแก้ไขข้อบกพร่องของโปรแกรม
ตัวอย่างลำดับของ semantic version
- version 0.3.10 จะมาก่อน 0.10.3
- version 0.1.1 จะมาก่อน 1.0.0
- version 1.100.100 จะมาก่อน 10.10.10
สำหรับใครที่อยากเขียน library node.js เพื่อ publish ลง npm เป็นของตัวเอง สำคัญเป็นอย่างมากที่จะต้องมีบอก version ตามมาตรฐานของ semver
Semver Range
ในการติดตั้ง node.js library เราสามารถ ใส่ใน package.json เป็น version ตรง ๆ ได้เลย (fixed version number) เช่น "react": "17.0.1"
หรือ semver range เช่น การใช้เครื่องหมาย tilde (~) "typescript": "~3.2.2"
โดยการที่เราติดตั้ง library แบบ fixed version number ในบล็อกที่ผมอ่านเขาบอกว่าเราควรหลีกเลี่ยงการใช้ fixed version range
semver range มีประโยชน์อย่างมาก มีเพื่อช่วยให้เราได้ซอฟต์แวร์เวอร์ชันใหม่ ๆ เช่น อยากอนุญาตให้ เลือก patch update ที่ใหม่ล่าสุดก็ทำได้ เช่น ~1.3.0 ถ้ามีเวอร์ชันใหม่อย่าง 1.3.2 มา โปรแกรมเราก็สามารถอัปเกรดไปที่ version ล่าสุดนั้นได้
simplest semver range
semver range ที่ง่ายที่สุดคือ "*"
range ซึ่งเป็นการบอกว่าเราอนุญาตให้ลงซอฟต์แวร์ version ไหนก็ได้ที่หาได้ โดย default แล้วก็จะเป็น "latest"
แต่ ๆ เราควรหลีกเลี่ยงที่จะใช้ semver range ตัวนี้เพราะว่า หากวันนึงเราจะ install โปรแกรมใหม่อีกครั้งอาจทำให้โปรแกรมเราทำงานผิดพลาด ไม่เหมือนเดิม เนื่องจาก library ที่เราติดตั้ง วันนึงอาจจะมี major update ที่มี breaking changes บางอย่าง
implicit and explicit sermver range
ตัวอย่างอีกอันของ semver range คือ การระบุ major version อย่างเดียว เช่น "2"
จะเป็นการครอบคลุมทุก minor และ patch update ที่น้อยกว่า major update 3
หรือ ระบุ major กับ minor เช่น "2.4"
ครอบคลุมทุก patch update ที่มีเลข version น้อยกว่า 2.5
โดยตัวอย่างข้างต้นสามารถเขียนแบบ explicit ได้ โดยใช้ตัวอักษร x หรือเครื่องหมายดอกจัน (*) เช่น "2.x.x"
และ "2.4.*"
เพิ่มเติมอีกนิด semver range ยังสามารถใช้เครื่องหมายอื่น ๆ ได้อีก เช่น -, <, <=, >, >=
Tilde (~) and Caret (^) shorthand
shorthand range เรามักจะเห็นสิ่งนี้บ่อย ๆ ใน package.json หลังจากที่ install บาง library
- การนำเครื่องหมาย tilde (~) มาเป็น prefix หน้าเลข version เป็นการบอกว่าเราอนุญาตให้ update patch ให้เป็น version ล่าสุด เช่น
"~1.2.3"
สามารถเขียนได้อีกแบบเป็น">=1.2.3 <1.3.0"
- การนำเครื่องหมาย caret (^) มาเป็น prefix หน้าเลข version เป็นการบอกว่าเราอนุญาตให้ update minor และ patch ให้เป็น version ล่าสุด เช่น
"^1.2.3"
สามารถเขียนได้อีกแบบเป็น">=1.2.3 <2.0.0"
ทำไม semver range ถึงมีความสำคัญกับโลกของ opensource ?
ลองนึกภาพ library ที่มี hierarchy
fruitshop-app
└─┬fruit@1.0.0
└─┬apple@1.0.0
└──seed@1.0.0 < needs critical bug-fix
วันนึงมีนักพัฒนาเจอบั๊กอยู่ใน seed@1.0.0
และแก้เป็น seed@1.0.1
หาก dependencies ของเราใช้การใส่เลข version ตรง ๆ (fixed semver) สิ่งที่เกิดขึ้น คือ
seed
แก้บั๊กแล้ว publish version ใหม่เป็นseed@1.0.1
apple
อัปเดตseed@1.0.1
แล้ว publish ตัวเองเป็นapple@1.0.1
fruit
อัปเดตapple@1.0.1
แล้ว publish ตัวเองเป็นfruit@1.0.1
fruitshop-app
อัปเดตfruit@1.0.1
- สุดท้าย
fruitshop-app
จะได้seed@1.0.1
apple@1.0.1
fruit@1.0.1
หลังจากทำการnpm install
คงลำบากน่าดูหาก dependency ตัวใดตัวนึงมีการ update และคงเสียเวลาไปพอสมควร
เปลี่ยนใหม่หาก apple
ใช้ semver range
{
"name": "apple",
"version": "1.0.0",
"dependencies": {
"seed": "~1.0.0"
}
}
ลองมาดู workflow ของการอัปเดต application กัน
seed
แก้บั๊กแล้ว publishseed@1.0.1
- fruitshop-app ทำการ clean
npm install
ครั้งถัดไปจะได้seed@1.0.1
เพราะว่าที่ apple เราบอกว่าอนุญาตให้ติดตั้งซอฟต์แวร์ที่ใช้ patch version ใหม่ได้ภายใต้ scope"1.0.x"
หวังว่าเนื้อหาจะมีประโยชน์ไม่มากก็น้อยกับผู้อ่านทุกท่าน แล้วเจอกันใหม่ครับ :)
อ้างอิง
- https://nodesource.com/blog/semver-a-primer
- https://github.com/npm/node-semver
- https://docs.npmjs.com/about-semantic-versioning
Special Thanks
ขอขอบคุณ “สำนักงานส่งเสริมเศรษฐกิจดิจิทัล (depa)” และคณาจารย์ “คณะเทคโนโลยีสารสนเทศ มจธ. (SIT)” ที่ให้การสนับสนุน “ทุนเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล (KMUTT-depa)” ซึ่งเป็นทุนที่มอบความรู้ ทักษะและโอกาสดีในการฝึกฝนพัฒนาทักษะที่มีอยู่ให้เฉียบคมมากยิ่งขึ้นครับ ❤