4 điểm bởi GN⁺ 2024-11-04 | 1 bình luận | Chia sẻ qua WhatsApp
  • Cash là một thư viện thay thế jQuery rất nhỏ gọn, cung cấp cú pháp kiểu jQuery để thao tác DOM trên các trình duyệt hiện đại hỗ trợ IE11+
  • Thư viện tận dụng các tính năng của trình duyệt hiện đại để giảm kích thước codebase, đồng thời cho phép dùng các phương thức chaining quen thuộc với kích thước tệp nhỏ hơn nhiều
  • Dù không đặt mục tiêu tương đương 100% tính năng với jQuery, thư viện vẫn bao phủ phần lớn các trường hợp sử dụng hằng ngày và API được triển khai nhìn chung tương thích với jQuery
  • Kích thước ở bản minified & gzipped là 6KB, nhỏ hơn 76,6% so với 24.4KB của jQuery Slim 3.4.1, và còn có thể giảm thêm bằng partial builds
  • Thư viện hỗ trợ codebase TypeScript, kiểu TypeScript được sinh từ mã nguồn, namespace event, và partial build cho phép loại bỏ từng phương thức riêng lẻ

Vấn đề mà Cash giải quyết

  • Cash là lựa chọn thay thế jQuery cho các trình duyệt hiện đại, cung cấp bộ chọn $() kiểu jQuery và các phương thức collection có thể chaining để thao tác DOM
  • Mục tiêu hỗ trợ là các trình duyệt IE11+
  • Dù không nhằm triển khai toàn bộ tính năng của jQuery, các tính năng mà Cash có vẫn được thiết kế để phần lớn tương thích với API của jQuery
  • Người dùng chuyển từ jQuery sang có thể xem migration guide

So sánh kích thước và tính năng

  • Trong so sánh kích thước tệp, Cash nhỏ hơn Zepto 1.2.0 và jQuery Slim 3.4.1
    • Unminified: 36.5KB
    • Minified: 16KB
    • Minified & Gzipped: 6KB
  • jQuery Slim 3.4.1 có kích thước 24.4KB ở bản minified & gzipped, và Cash giúp giảm 76,6% kích thước so với mức này
  • Nếu cần bundle nhỏ hơn nữa, có thể dùng partial builds
  • Về mặt tính năng, Cash cung cấp hỗ trợ trình duyệt hiện đại, được bảo trì tích cực, namespace event, codebase TypeScript, và kiểu TypeScript được sinh từ mã nguồn
  • Partial builds của Cash cho phép loại bỏ từng phương thức riêng lẻ, trong khi Zepto và jQuery Slim được đánh dấu là loại bỏ theo toàn bộ module

Cách sử dụng

  • Có thể tải Cash từ jsDelivr và dùng trực tiếp trong trình duyệt
<script src="https://cdn.jsdelivr.net/npm/cash-dom/…;
<script>
  $(function () {
    $('html').addClass ( 'dom-loaded' );
    $('<footer>Appended with Cash</footer>').appendTo ( document.body );
  });
</script>
  • Gói npm được phát hành với tên cash-dom
npm install --save cash-dom
import $ from "cash-dom";

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

Cấu trúc API

  • $()phương thức selector cốt lõi của Cash và trả về một collection node có thể thao tác
  • Khi truyền vào một hàm, hàm đó sẽ được chạy tại thời điểm DOM sẵn sàng
  • $() có thể nhận selector, DOM node, nodeList, HTML string, Cash collection, và document ready callback
  • Cash cung cấp ba nhóm API chính
    • Query selector
    • Phương thức collection
    • Các phương thức thư viện của đối tượng $ toàn cục

Phương thức collection

  • Các phương thức collection được gọi sau khi tạo collection bằng $(), theo cách như $(element).addClass(className)
  • Các nhóm chức năng được cung cấp gồm thuộc tính, collection, CSS, dữ liệu, kích thước, hiệu ứng, sự kiện, form, thao tác DOM, offset và duyệt cây
  • Các phương thức chính gồm
    • Class/thuộc tính: addClass, removeClass, toggleClass, attr, prop, removeAttr
    • Xử lý collection: add, each, eq, filter, first, get, map, slice
    • Thao tác DOM: append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
    • Sự kiện: on, off, one, ready, trigger
    • Duyệt cây: find, children, closest, parent, parents, siblings, next, prev
  • Có một số extra methods, nhưng mặc định bị vô hiệu hóa
  • $.fn là prototype chính của collection, cho phép thêm các phương thức tùy chỉnh vào mọi collection theo kiểu plugin

Phương thức Cash toàn cục

  • Đối tượng $ toàn cục bao gồm các phương thức kiểm tra kiểu và utility
  • Các phương thức kiểm tra kiểu gồm $.isArray, $.isFunction, $.isNumeric, $.isPlainObject, $.isWindow
  • Các utility gồm $.guid, $.each, $.extend, $.parseHTML, $.unique
  • $.extend mở rộng đối tượng đích bằng các thuộc tính của đối tượng nguồn, và cũng hỗ trợ mở rộng sâu
  • $.parseHTML trả về collection từ chuỗi HTML, còn $.unique trả về một mảng mới đã loại bỏ phần tử trùng lặp

Mở rộng và đóng góp

  • Cash có thể được mở rộng bằng các phương thức tùy chỉnh, và cách mở rộng được tổng hợp trong extending Cash
  • Có thể tạo yêu cầu lỗi hoặc tính năng qua GitHub issue
  • Quy trình làm pull request gồm clone kho mã, cài dependency, tự động biên dịch lại bằng npm run dev, chạy kiểm thử bằng npm run test, và cập nhật README nếu cần
  • Giấy phép là MIT

1 bình luận

 
GN⁺ 2024-11-04
Ý kiến trên Hacker News
  • Trình duyệt ngày nay đã tốt hơn nhiều, nên để đơn giản hóa thao tác DOM, thường chỉ cần hai dòng alias dưới đây là đủ
    dqs = document.querySelector.bind(document);
    dqsA = document.querySelectorAll.bind(document);
    Vì vậy có thể dùng như dqs('#country') thay cho document.querySelector('#country'), và dqsA('.city') thay cho document.querySelectorAll('.city')
    Phần còn lại cứ dùng hàm native của trình duyệt cũng ổn, và thường import từ module kiểu import { dqs, dqsA } from '/lib/js/dqs.js';
    https://github.com/no-gravity/dqs.js

    • Bản thân hai dòng bind querySelector/querySelectorAll có vẻ hữu ích và hợp lý, nhưng đưa chúng vào bằng import thì hơi quá
      Chỉ là hai dòng đơn giản thôi, không có lý do gì để biến thành dependency; cứ copy-paste là được
    • Không phải hàm tiện ích nào cũng chỉ một dòng
      Ví dụ $.fn.one() hay $.fn.on() để xử lý nhiều event thì dùng bằng jQuery/Cash dễ hơn, và nhìn implementation bên trong sẽ thấy khá nhiều việc: https://github.com/fabiospampinato/cash/blob/master/src/even...
    • querySelectorAll() không phải là live collection, nên thường chuyển ngay sang mảng như thế này
      dqsA = s => Array.from(document.querySelectorAll(s));
      Làm vậy có thể dùng trực tiếp các array method như .map() hay .filter() trên kết quả, nên vẫn còn cảm giác gần giống jQuery
    • Gần đây tôi thử loại bỏ dependency vào React, nhưng ở phần xử lý event thì khác biệt giữa các trình duyệt vẫn còn khá lớn
      Chẳng hạn trong lúc test, trên Safari event select của một phần tử hoàn toàn không phát sinh, còn ở một số trình duyệt thì khi chỉ di chuyển caret cũng không có event
      Dù không cần các tính năng React như component, state, props, chỉ dùng hàm native của trình duyệt vẫn chưa đủ; giá trị của React DOM trong việc che lấp khác biệt trình duyệt đã hiện rõ
    • Trông giống bling.js: https://gist.github.com/paulirish/12fb951a8b893a454b32
  • Sau khi gần như gỡ hết polyfill, lợi thế bền vững còn lại của jQuery theo tôi là tự động xử lý danh sách
    Khả năng bỏ chọn mọi nút trong một form chỉ bằng một lần gọi vẫn khó có thứ khác bắt kịp, và truy vấn phần tử cha cũng vậy
    Tuy nhiên vấn đề lớn nhất về mặt implementation là khi danh sách rỗng thì nó thất bại một cách im lặng
    Vì đã phải sửa quá nhiều bug kiểu này phát sinh khi về sau refactor cây DOM cho mục đích layout, nên nếu bây giờ làm lại jQuery, mặc định tôi sẽ cho báo lỗi với tập rỗng, và chỉ thất bại im lặng qua chain call hoặc flag khi thật sự không quan tâm
    Trước đây tôi từng dành vài giờ xem liệu có thể tách Sizzle ra và đổi theo kiểu này không, nhưng rồi không đi tiếp
    Rốt cuộc jQuery cũng liên quan đến cuộc tranh luận cũ library vs framework, và sau thời gian dài làm ứng dụng một trang bằng các framework khổng lồ, tôi có cảm giác lại sắp đến gần “thung lũng vỡ mộng”

    • Việc không phải lo selector nào thật sự khớp với phần tử nào là điểm hấp dẫn của jQuery với nhiều người
      Nếu ra lệnh ẩn tất cả .foo thì có sẽ ẩn, không có thì chẳng có gì xảy ra; kiểu fire and forget này giống CSS
      Viết .foo { color: red; } mà trong tài liệu không có .foo thì ngoài chút overhead nhỏ cũng không có tác dụng phụ
    • Trên trình duyệt hiện đại, sửa mọi mục trong kết quả query cũng có thể viết khá gọn trong một dòng
      document.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);
      Đây là cách tận dụng NodeList có thể duyệt và iterator helper
      Nhiều truy vấn phần tử cha có thể xử lý bằng element.closest()
    • Tôi hoàn toàn đồng ý rằng mặc định nên nghiêm ngặt
      Code dựa trên jQuery đòi hỏi mọi developer phải biết tất cả selector trong dự án rồi cập nhật khi DOM thay đổi, điều này hiển nhiên là bất khả thi
      Có vẻ có thể thay bằng một implementation tự viết cho hàm khởi tạo jQuery để buộc kiểm tra độ dài
  • Trong bối cảnh các website phổ biến đang phát tán đúng nghĩa JavaScript tính bằng megabyte, tôi không hiểu vì sao lại phải viết lại cả một thư viện ít tính năng hơn chỉ để tiết kiệm 50KB

    • Tách khỏi bản thân package này, mỗi lần thấy lập luận kiểu này trong câu chuyện về jQuery là tôi lại bối rối
      Nhiều bài viết phản đối dùng jQuery vì kích thước package và hạn chế băng thông, đồng thời lại bênh vực SPA framework dùng băng thông nhiều hơn hẳn
      Đó đúng là kiểu suy luận cargo cult hoàn toàn vô lý
    • Lập luận đó nghe giống như “các chuỗi fast food phổ biến bán bữa ăn 1600 calorie thì sao còn làm salad cho bữa trưa” hoặc “nợ quốc gia gần 35 nghìn tỷ USD thì sao còn so sánh lãi suất vay mua nhà”
      Cả ba trường hợp đều có cùng câu trả lời: thứ lớn kia không phải là tôi, còn tôi đang làm một thứ khác nhỏ hơn
      Một câu trả lời khác là, nếu quá lớn là vấn đề, thì trở nên nhỏ hơn nghe giống một giải pháp
      Cuối cùng có vẻ điều muốn hỏi là vì sao developer lại dành thời gian viết lại thư viện, nhưng chuyện đó cũng không đáng ngạc nhiên đến vậy
      Một phần đáng kể của lập trình là viết lại những thứ đã có, vì nhu cầu công việc, vì cần hành vi hoặc đặc tính hiệu năng hơi khác, hoặc đơn giản vì muốn học cách nó hoạt động
    • Thay vì hỏi “tại sao phải làm vậy?”, nếu chấp nhận dependency nhỏ, các dependency cũng có thể trở nên nhỏ hơn
    • Website phổ biến gần như là rác phục vụ phân phối quảng cáo, không nên lấy chúng làm chuẩn cho việc chúng ta phải làm
    • Vẫn có những người cố gửi tổng lượng JavaScript dưới 50KB
  • Nếu đang tìm một lựa chọn thay thế jQuery, vì chờ jQuery 4.0 quá lâu nên cuối cùng tôi đã tự làm một thứ giống jQuery với vài khác biệt quan trọng
    Animation, tween và timeline dùng CSS thuần thay vì hệ thống tùy biến của jQuery; xử lý minh bạch cả phần tử đơn lẻ lẫn danh sách; và hướng tới cách dùng inline

    • Tài liệu tự mâu thuẫn với nhau
      me() được nói là trả về 1 phần tử, hoặc phần tử đầu tiên, hoặc null; còn any() được nói là trả về một mảng hoặc mảng rỗng
      Nhưng các ví dụ bên dưới như any('button')?.forEach(...), any('button')?.map(...) lại hàm ý rằng nó cũng có thể là null
      Tôi thấy khó hiểu không biết any() có luôn trả về mảng như mô tả ở trên hay cũng có thể là null như ví dụ bên dưới
    • Hay
      Tôi đặc biệt quan tâm đến tính cục bộ của hành vi
      Tôi tò mò trải nghiệm dùng currentScript.parentElement ra sao
      Khi khảo sát nhanh tháng trước, tôi có ấn tượng rằng có lẽ nó không đáng tin trong một số trường hợp ngách, nhưng không nhớ chính xác là khi nào
      Tôi chưa đào sâu, và rất vui khi thấy có người làm cho nó hoạt động tốt
      Nếu giả định không phải async hay module, thì kể cả tải 3 script liên tiếp, chẳng phải currentScript.parentElement vẫn nên hoạt động trên mọi trình duyệt sao?
      SvelteKit cũng từng bàn chuyện này, và cuối cùng đã triển khai ID ngẫu nhiên để chỉ định phần tử đích: https://github.com/sveltejs/kit/issues/2221
  • Khi xem hướng dẫn migration, tôi học được vài tính năng jQuery làm được nhưng Cash thì không; trước đây tôi chưa biết và có thể một ngày nào đó sẽ thử dùng
    https://github.com/fabiospampinato/cash/blob/master/docs/mig...

  • Như một mục tiêu mở rộng, sẽ rất hay nếu dùng phép màu template string của TypeScript để suy luận chính xác kiểu phần tử
    Ví dụ có thể suy luận tĩnh rằng $('div#name')HTMLDivElement

    • Gói đó được gọi là typed-query-selector
      Ví dụ sử dụng thực tế ở đây: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
    • Elixir và một số ngôn ngữ có thể làm được chuyện như vậy bằng pattern matching và hệ thống kiểu, nhưng nhiều ngôn ngữ thì không
      Tôi không biết TypeScript có làm được không, và cũng chưa thấy rõ có thể làm bằng cách nào
  • Tôi nghe nói jQuery 4 là lựa chọn thay thế jQuery dành cho các trình duyệt hiện đại

  • Ban đầu tôi dùng cái này trong extension trình duyệt đang làm, nhưng cuối cùng đã chuyển sang một thư viện JSX
    Khi vượt ra khỏi phạm vi “ứng dụng đơn giản”, jQuery rất nhanh trở thành mã khó suy luận, và với tư cách người từng tự làm một thư viện lấy cảm hứng từ jQuery, tôi cũng cảm thấy vậy
    Rốt cuộc vẫn phải dùng công cụ phù hợp với công việc
    [1]: https://github.com/aleclarson/dough
    Nếu bạn có thể xử lý tốt jQuery trong ứng dụng vừa và lớn thì ổn, nhưng không hợp gu của tôi

  • Trước đây khi cố giảm JS, tôi từng dùng https://github.com/filamentgroup/shoestring
    Lý do chính là nó cung cấp custom build chỉ gồm những gì thật sự cần
    Cash dường như cũng có tính năng tương tự, nhưng trong tài liệu thì hơi bị giấu kỹ hơn: https://github.com/fabiospampinato/cash/blob/master/docs/par...
    Nếu dùng, có lẽ tôi sẽ thử hướng đó trước
    Dù vậy, tôi vẫn nghĩ dùng trực tiếp những gì trình duyệt ngày nay cung cấp là lựa chọn tốt hơn
    Thực tế chúng khá tốt, và jQuery không còn thực sự cần thiết nữa
    Điều này càng rõ hơn khi ngay cả một lựa chọn thay thế jQuery nhỏ cũng đã 6kB, trong khi Preact, một thư viện kiểu React, chỉ bằng một nửa kích thước đó

  • Tôi không chắc nó giúp được gì nhiều hơn việc đặt alias cho các Web API đã có sẵn