4 คะแนน โดย GN⁺ 2024-11-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Cash เป็นไลบรารีทางเลือกแทน jQuery ที่มีขนาดเล็กมาก โดยให้ไวยากรณ์สไตล์ jQuery สำหรับจัดการ DOM บนเบราว์เซอร์สมัยใหม่ IE11+
  • ใช้ประโยชน์จากความสามารถของเบราว์เซอร์สมัยใหม่เพื่อลด codebase และทำให้ใช้ เมธอดแบบ chaining ที่คุ้นเคยได้ด้วยขนาดไฟล์ที่เล็กลงมาก
  • ไม่ได้ตั้งเป้าให้มี ความเทียบเท่าด้านฟีเจอร์ 100% กับ jQuery แต่ครอบคลุมกรณีใช้งานประจำวันส่วนใหญ่ และ API ที่นำมาใช้งานโดยรวมเข้ากันได้กับ jQuery
  • ขนาดเมื่อ minified & gzipped อยู่ที่ 6KB เล็กกว่า jQuery Slim 3.4.1 ที่ 24.4KB อยู่ 76.6% และสามารถลดลงได้อีกด้วย partial builds
  • รองรับ codebase แบบ TypeScript, TypeScript types ที่สร้างจากโค้ด, อีเวนต์แบบ namespaced และ partial builds ที่สามารถตัดเมธอดรายตัวออกได้

ปัญหาที่ Cash แก้ไข

  • Cash เป็นทางเลือกแทน jQuery สำหรับเบราว์เซอร์สมัยใหม่ โดยให้ selector $() สไตล์ jQuery และเมธอดคอลเลกชันที่ chain ได้สำหรับจัดการ DOM
  • กลุ่มเป้าหมายที่รองรับคือเบราว์เซอร์ IE11+
  • เป้าหมายไม่ใช่การนำฟีเจอร์ทั้งหมดของ jQuery มาทำซ้ำเหมือนเดิม แต่ฟีเจอร์ที่ Cash นำมาใช้งานถูกออกแบบให้เข้ากันได้กับ jQuery API เป็นส่วนใหญ่
  • ผู้ใช้ที่ย้ายมาจาก jQuery สามารถดู migration guide ได้

เปรียบเทียบขนาดและฟีเจอร์

  • ในการเปรียบเทียบขนาดไฟล์ Cash มีขนาดเล็กกว่า Zepto 1.2.0 และ jQuery Slim 3.4.1
    • Unminified: 36.5KB
    • Minified: 16KB
    • Minified & Gzipped: 6KB
  • jQuery Slim 3.4.1 มีขนาด 24.4KB เมื่อ minified & gzipped ส่วน Cash ลดขนาดลงได้ 76.6% เมื่อเทียบกัน
  • หากต้องการบันเดิลที่เล็กกว่านี้ สามารถใช้ partial builds ได้
  • จากการเปรียบเทียบฟีเจอร์ Cash รองรับเบราว์เซอร์สมัยใหม่, มีการบำรุงรักษาอย่างต่อเนื่อง, รองรับ อีเวนต์แบบ namespaced, codebase แบบ TypeScript และ TypeScript types ที่สร้างจากโค้ด
  • ใน Cash นั้น Partial builds สามารถตัดเมธอดรายตัวออกได้ ส่วน Zepto และ jQuery Slim ระบุว่าเป็นการตัดออกในระดับโมดูลทั้งหมด

วิธีใช้งาน

  • สามารถโหลด Cash จาก jsDelivr และใช้งานบนเบราว์เซอร์ได้ทันที
<script src="https://cdn.jsdelivr.net/npm/cash-dom/…;
<script>
  $(function () {
    $('html').addClass ( 'dom-loaded' );
    $('<footer>Appended with Cash</footer>').appendTo ( document.body );
  });
</script>
  • แพ็กเกจ npm มีให้ใช้ในชื่อ cash-dom
npm install --save cash-dom
import $ from "cash-dom";

$(function () {
  $('html').addClass ( 'dom-loaded' );
  $('<footer>Appended with Cash</footer>').appendTo ( document.body );
});

โครงสร้าง API

  • $() คือ เมธอด selector หลักของ Cash และคืนค่าคอลเลกชันของโหนดที่สามารถจัดการได้
  • หากส่งฟังก์ชันเข้าไป ฟังก์ชันนั้นจะถูกเรียกเมื่อ DOM พร้อมใช้งาน
  • $() รับ selector, DOM node, nodeList, HTML string, Cash collection และ callback เมื่อ document ready ได้
  • Cash มี API หลัก ๆ สามประเภท
    • query selector
    • เมธอดคอลเลกชัน
    • เมธอดไลบรารีของอ็อบเจ็กต์ $ แบบ global

เมธอดคอลเลกชัน

  • เมธอดคอลเลกชันจะถูกเรียกหลังจากสร้างคอลเลกชันด้วย $() เช่น $(element).addClass(className)
  • หมวดหมู่ที่มีให้แบ่งเป็น แอตทริบิวต์, คอลเลกชัน, CSS, ข้อมูล, มิติ, เอฟเฟกต์, อีเวนต์, ฟอร์ม, การจัดการ DOM, offset และการนำทาง
  • เมธอดหลัก ๆ มีดังนี้
    • คลาส/แอตทริบิวต์: addClass, removeClass, toggleClass, attr, prop, removeAttr
    • การจัดการคอลเลกชัน: add, each, eq, filter, first, get, map, slice
    • การจัดการ DOM: append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
    • อีเวนต์: on, off, one, ready, trigger
    • การนำทาง: find, children, closest, parent, parents, siblings, next, prev
  • มี extra methods บางส่วนให้ใช้ แต่ถูกปิดไว้เป็นค่าเริ่มต้น
  • $.fn คือ prototype หลักของคอลเลกชัน และสามารถเพิ่มเมธอดกำหนดเองให้กับทุกคอลเลกชันได้เหมือนปลั๊กอิน

เมธอด Cash แบบ global

  • อ็อบเจ็กต์ $ แบบ global มีเมธอดตรวจสอบชนิดและ utility รวมอยู่ด้วย
  • เมธอดตรวจสอบชนิดมี $.isArray, $.isFunction, $.isNumeric, $.isPlainObject, $.isWindow
  • utility มี $.guid, $.each, $.extend, $.parseHTML, $.unique
  • $.extend ขยายอ็อบเจ็กต์เป้าหมายด้วยพร็อพเพอร์ตีของอ็อบเจ็กต์ต้นทาง และรองรับ deep extend ด้วย
  • $.parseHTML คืนค่าคอลเลกชันจากสตริง HTML และ $.unique คืนค่าอาร์เรย์ใหม่ที่ลบค่าซ้ำออกแล้ว

การขยายและการมีส่วนร่วม

  • Cash สามารถขยายด้วยเมธอดกำหนดเองได้ โดยมีวิธีขยายอธิบายไว้ใน extending Cash
  • สามารถเปิดปัญหาหรือคำขอฟีเจอร์ได้ผ่าน GitHub issue
  • workflow สำหรับ Pull request คือ clone repository, ติดตั้ง dependencies, ใช้ npm run dev เพื่อ recompile อัตโนมัติ, รันเทสต์ด้วย npm run test และอัปเดต README หากจำเป็น
  • ไลเซนส์คือ MIT

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

 
GN⁺ 2024-11-04
ความคิดเห็นจาก Hacker News
  • ทุกวันนี้เบราว์เซอร์ดีขึ้นมาก จนในหลายกรณีแค่ alias สองบรรทัด ด้านล่างก็พอสำหรับทำให้การจัดการ DOM ง่ายขึ้นแล้ว
    dqs = document.querySelector.bind(document);
    dqsA = document.querySelectorAll.bind(document);
    ดังนั้นจึงใช้ dqs('#country') แทน document.querySelector('#country') และ dqsA('.city') แทน document.querySelectorAll('.city') ได้
    ส่วนที่เหลือใช้ ฟังก์ชันเนทีฟของเบราว์เซอร์ ไปก็ได้ และโดยปกติก็มัก import จากโมดูลแบบ import { dqs, dqsA } from '/lib/js/dqs.js';
    https://github.com/no-gravity/dqs.js

    • สองบรรทัดที่ bind querySelector/querySelectorAll เองนั้นดูมีประโยชน์และสมเหตุสมผล แต่การเอามาใช้ผ่าน import ดูเกินจำเป็น
      มันเป็นแค่ สองบรรทัดง่าย ๆ ไม่มีเหตุผลต้องทำเป็น dependency แค่คัดลอกมาแปะก็พอ
    • ไม่ใช่ทุกฟังก์ชัน utility จะสั้นแค่บรรทัดเดียว
      ตัวอย่างเช่น $.fn.one() หรือ $.fn.on() ที่จัดการหลาย event ใช้ผ่าน jQuery/Cash จะง่ายกว่า และถ้าดู implementation ภายในก็มีงานอยู่ไม่น้อย: https://github.com/fabiospampinato/cash/blob/master/src/even...
    • querySelectorAll() ไม่ใช่ live collection เลยมักแปลงเป็น array ทันทีแบบนี้
      dqsA = s => Array.from(document.querySelectorAll(s));
      แบบนี้จะใช้เมธอดของ array อย่าง .map() หรือ .filter() กับผลลัพธ์ได้ทันที ให้ความรู้สึกคล้าย jQuery
    • ช่วงหลังลองเอา dependency บน React ออก แต่ในส่วน การจัดการ event ความต่างระหว่างเบราว์เซอร์ยังค่อนข้างหนักอยู่
      เช่น ระหว่างทดสอบ ใน Safari event select ของบาง element ไม่เกิดขึ้นเลย และในบางเบราว์เซอร์ ถ้าแค่ย้าย caret ก็ไม่มี event เกิดขึ้น
      ต่อให้ไม่ต้องใช้ฟีเจอร์ของ React อย่าง component, state, props ฟังก์ชันเนทีฟของเบราว์เซอร์อย่างเดียวก็ยังไม่พอ และเห็นคุณค่าของ React DOM ที่ช่วยกลบความต่างระหว่างเบราว์เซอร์
    • ดูคล้าย bling.js: https://gist.github.com/paulirish/12fb951a8b893a454b32
  • หลังจากถอด polyfill ออกไปเกือบหมดแล้ว ข้อดีที่ยังคงอยู่ของ jQuery คือ การจัดการ list อัตโนมัติ
    ความสามารถในการยกเลิกการเลือกปุ่มทั้งหมดใน form ด้วยการเรียกครั้งเดียวยังหาตัวอื่นเทียบได้ยาก และ parent query ก็เช่นกัน
    แต่ปัญหาใหญ่ที่สุดในเชิง implementation คือเมื่อ list ว่าง มันจะล้มเหลวแบบเงียบ ๆ
    ผมเคยแก้บั๊กแบบนี้มาเยอะมาก ตอน refactor DOM tree เพื่อจุดประสงค์ด้าน layout ในภายหลัง ดังนั้นถ้าจะสร้าง jQuery ใหม่ตอนนี้ ค่าเริ่มต้นควร error เมื่อเจอชุดว่าง และมี chain call หรือ flag ให้ล้มเหลวแบบเงียบ ๆ เฉพาะเวลาที่ไม่สนใจจริง ๆ เท่านั้น
    เมื่อก่อนเคยใช้เวลาหลายชั่วโมงดูว่าจะดึง Sizzle ออกแล้วเปลี่ยนเป็นแบบนี้ได้ไหม แต่ก็ไม่ได้ทำต่อ
    ท้ายที่สุด jQuery ก็เชื่อมโยงกับข้อถกเถียงเก่าเรื่อง library vs framework ด้วย และหลังจากทำ single-page app ด้วย framework ขนาดใหญ่มานาน ก็รู้สึกเหมือนใกล้กลับเข้าสู่หุบเขาแห่งความผิดหวังอีกครั้ง

    • จุดที่ไม่ต้องกังวลว่า selector ใดจะ match กับ element จริงหรือไม่ เป็นเสน่ห์ของ jQuery สำหรับหลายคน
      ถ้าสั่งให้ซ่อน .foo ทั้งหมด ถ้ามีก็ซ่อน ถ้าไม่มีก็ไม่เกิดอะไร เป็นแนว fire and forget คล้าย CSS
      ถ้าเขียน .foo { color: red; } แล้วในเอกสารไม่มี .foo ก็ไม่มี side effect นอกจาก overhead เล็กน้อย
    • ในเบราว์เซอร์รุ่นใหม่ การแก้ไขทุก item ในผลลัพธ์ query ก็ทำได้ค่อนข้างเรียบร้อยในบรรทัดเดียว
      document.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);
      นี่เป็นวิธีที่ใช้ NodeList ที่วนซ้ำได้และตัวช่วย iterator
      parent query จำนวนมากจัดการได้ด้วย element.closest()
    • เห็นด้วยอย่างยิ่งว่าค่าเริ่มต้นควรเข้มงวด
      โค้ดที่ใช้ jQuery ต้องอาศัยให้นักพัฒนาทุกคนรู้ selector ทุกตัวในโปรเจกต์ แล้วคอยอัปเดตเมื่อ DOM เปลี่ยน ซึ่งแน่นอนว่าเป็นไปไม่ได้
      ดูเหมือนจะเป็นไปได้ที่จะเปลี่ยนไปใช้ฟังก์ชัน initialization ของ jQuery ที่ implement เอง เพื่อบังคับตรวจสอบความยาว
  • ในสถานการณ์ที่เว็บไซต์กระแสหลักปล่อย JavaScript เป็น ระดับเมกะไบต์ จริง ๆ ผมไม่เข้าใจว่าทำไมต้องเขียน library ที่มีฟีเจอร์น้อยกว่าใหม่ทั้งก้อนเพื่อประหยัด 50KB

    • แยกจากแพ็กเกจนี้เอง ผมงงทุกครั้งที่เห็นตรรกะแบบนี้ในเรื่อง jQuery
      หลายบทความคัดค้านการใช้ jQuery เพราะขนาดแพ็กเกจและข้อจำกัดด้านแบนด์วิดท์ แต่ในขณะเดียวกันกลับสนับสนุน SPA framework ที่ใช้แบนด์วิดท์มากกว่ามาก
      เป็นการให้เหตุผลแบบ cargo cult ที่ไร้สาระโดยสิ้นเชิง
    • ตรรกะนั้นฟังคล้ายกับ “ร้านฟาสต์ฟู้ดกระแสหลักขายมื้ออาหาร 1600 แคลอรี แล้วทำไมต้องทำสลัดเป็นมื้อกลางวัน” หรือ “หนี้สาธารณะใกล้ 35 ล้านล้านดอลลาร์ แล้วทำไมต้องเปรียบเทียบอัตราดอกเบี้ยจำนองบ้าน”
      ทั้งสามกรณีมีคำตอบเหมือนกัน: ของที่ใหญ่นั้นไม่ใช่ผม และผมกำลังทำสิ่งอื่นที่เล็กกว่า
      อีกคำตอบหนึ่งคือ ถ้าสิ่งที่ใหญ่เกินไปเป็นปัญหา การทำให้เล็กลง ก็ดูเหมือนเป็นทางแก้
      สุดท้ายแล้วคำถามที่อยากถามคงเป็นว่าทำไมนักพัฒนาถึงใช้เวลาเขียน library ใหม่ แต่เรื่องนั้นก็ไม่น่าตกใจนัก
      งานเขียนโปรแกรมจำนวนมากคือการเขียนสิ่งที่มีอยู่แล้วใหม่ เพราะงานจำเป็นต้องใช้ ต้องการพฤติกรรมหรือคุณลักษณะด้าน performance ที่ต่างออกไปเล็กน้อย หรือแค่อยากเรียนรู้ว่ามันทำงานอย่างไร
    • แทนที่จะพูดว่า “ทำไปทำไม?” ถ้ายอมรับ dependency ขนาดเล็ก dependency ต่าง ๆ ก็อาจเล็กลงได้ด้วย
    • เว็บไซต์กระแสหลักแทบจะเป็นขยะสำหรับส่งโฆษณา และไม่ควรใช้เป็นมาตรฐานของสิ่งที่เราควรทำ
    • ยังมีคนที่พยายามส่ง JavaScript ทั้งหมดให้ต่ำกว่า 50KB อยู่
  • หากกำลังมองหาทางเลือกแทน jQuery ผมรอ jQuery 4.0 นานเกินไป สุดท้ายเลยทำอะไรที่คล้าย jQuery ขึ้นมาเองโดยมีความแตกต่างสำคัญอยู่บ้าง
    แอนิเมชัน, tween และ timeline ใช้ CSS ล้วนแทนระบบคัสตอมของ jQuery, จัดการทั้งอิลิเมนต์เดี่ยวและลิสต์ได้อย่างโปร่งใส และมุ่งให้ใช้แบบ inline

    • เอกสารขัดแย้งกันเอง
      me() ระบุว่าจะคืนค่าอิลิเมนต์ 1 ตัว หรืออิลิเมนต์แรก หรือ null ส่วน any() ระบุว่าจะคืนค่าเป็นอาร์เรย์หรืออาร์เรย์ว่าง
      แต่ตัวอย่างด้านล่างกลับสื่อว่าอาจเป็น null ได้ เช่น any('button')?.forEach(...), any('button')?.map(...)
      เลยสับสนว่า any() คืนค่าเป็นอาร์เรย์เสมอตามคำอธิบายข้างต้น หรือสามารถเป็น null ได้เหมือนในตัวอย่างด้านล่าง
    • ดี
      สนใจเป็นพิเศษในเรื่อง locality of behavior
      อยากรู้ว่าประสบการณ์ในการใช้ currentScript.parentElement เป็นอย่างไร
      ตอนที่สำรวจคร่าว ๆ เมื่อเดือนที่แล้ว ผมได้ความรู้สึกว่าน่าจะไม่น่าเชื่อถือในเคสบางมุม แต่จำไม่ได้แน่ชัดว่าเป็นเมื่อไหร่
      ยังไม่ได้ขุดลึก และดีใจที่ทำให้มันทำงานได้ดี
      ถ้าอยู่บนสมมติฐานว่าไม่ใช่ async หรือ module ต่อให้โหลดสคริปต์ติดกัน 3 ตัว currentScript.parentElement ก็น่าจะยังทำงานได้ในทุกเบราว์เซอร์ไม่ใช่หรือ
      SvelteKit ก็เคยถกเรื่องนี้ และสุดท้ายก็ implement ID แบบสุ่มเพื่อระบุอิลิเมนต์เป้าหมาย: https://github.com/sveltejs/kit/issues/2221
  • จากการดูคู่มือ migration ทำให้ได้รู้ฟีเจอร์บางอย่างที่ jQuery ทำได้แต่ Cash ทำไม่ได้ ซึ่งผมไม่เคยรู้มาก่อนและสักวันอาจน่าลองใช้
    https://github.com/fabiospampinato/cash/blob/master/docs/mig...

  • ในฐานะเป้าหมายเสริม ถ้าใช้เวทมนตร์ของ template string ใน TypeScript เพื่อ infer ชนิดของอิลิเมนต์ได้อย่างแม่นยำก็คงดี
    ตัวอย่างเช่นสามารถ infer แบบ static ได้ว่า $('div#name') คือ HTMLDivElement

    • แพ็กเกจนั้นเรียกว่า typed-query-selector
      ตัวอย่างการใช้งานจริงอยู่ที่นี่: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
    • Elixir และบางภาษาใช้ pattern matching กับระบบ type ทำสิ่งแบบนั้นได้ แต่หลายภาษาทำไม่ได้
      ไม่รู้ว่า TypeScript ทำได้หรือเปล่า และก็มองไม่ค่อยออกว่าจะทำอย่างไร
  • ได้ยินมาว่า jQuery 4 เป็น ทางเลือกแทน jQuery สำหรับเบราว์เซอร์สมัยใหม่

  • ตอนแรกผมใช้ตัวนี้ใน browser extension ที่กำลังทำอยู่ แต่สุดท้ายก็ย้ายไปใช้ ไลบรารี JSX
    พอเกินขอบเขตของ “แอปง่าย ๆ” ไปแล้ว jQuery จะกลายเป็นโค้ดที่อนุมานตามได้ยากอย่างรวดเร็ว และในฐานะคนที่เคยทำไลบรารีที่ได้แรงบันดาลใจจาก jQuery เองก็รู้สึกแบบนั้นเหมือนกัน
    สุดท้ายก็ต้องใช้เครื่องมือให้เหมาะกับงาน
    [1]: https://github.com/aleclarson/dough
    ถ้าคุณรับมือกับ jQuery ในแอปขนาดกลางถึงใหญ่ได้ดีก็โอเค แต่มันไม่ใช่รสนิยมของผม

  • เมื่อก่อนตอนพยายามลด JS ผมเคยใช้ https://github.com/filamentgroup/shoestring
    เหตุผลหลักคือมันมี custom build ที่ใส่เฉพาะสิ่งที่จำเป็นจริง ๆ
    Cash ก็ดูเหมือนจะมีฟีเจอร์คล้ายกัน แต่ถูกซ่อนไว้ในเอกสารมากกว่านิดหน่อย: https://github.com/fabiospampinato/cash/blob/master/docs/par...
    ถ้าจะใช้ ผมน่าจะลองทางนั้นก่อน
    ถึงอย่างนั้นก็ยังคิดว่าการใช้สิ่งที่เบราว์เซอร์ยุคนี้มีให้โดยตรงน่าจะเป็นตัวเลือกที่ดีกว่า
    ของจริงค่อนข้างดี และ jQuery ก็ไม่จำเป็นอีกต่อไปแล้ว
    โดยเฉพาะเมื่อเห็นว่าแม้แต่ทางเลือกแทน jQuery ขนาดเล็กก็ยัง 6kB ขณะที่ Preact ซึ่งเป็นไลบรารีคล้าย React มีขนาดแค่ครึ่งหนึ่งของมัน

  • ไม่ค่อยแน่ใจว่ามันช่วยได้มากไปกว่าการตั้ง alias ให้ Web API ที่มีอยู่แล้วหรือไม่