4 ポイント 投稿者 GN⁺ 2024-11-04 | 1件のコメント | WhatsAppで共有
  • Cash は、IE11+ のモダンブラウザで DOM を操作するための jQuery スタイル構文を提供する、非常に小さな jQuery 代替ライブラリ
  • モダンブラウザの機能を活用してコードベースを削減し、使い慣れた チェーン可能なメソッド をはるかに小さいファイルサイズで利用できる
  • jQuery と 100% の機能同等性 を目指しているわけではないが、日常的なユースケースの大半をカバーし、実装されている API はおおむね jQuery と互換性がある
  • サイズは minified & gzipped で 6KB で、jQuery Slim 3.4.1 の 24.4KB と比べて 76.6% 小さく、partial builds でさらに削減できる
  • TypeScript コードベース、コードから生成される TypeScript 型、名前空間付きイベント、個別メソッドを除外できる partial builds をサポート

Cash が解決する問題

  • Cash はモダンブラウザ向けの jQuery 代替で、DOM 操作のために jQuery スタイルの $() セレクタとチェーン可能なコレクションメソッドを提供する
  • サポート対象は 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 は minified & gzipped で 24.4KB であり、Cash はこれに対して 76.6% のサイズ削減効果がある
  • より小さいバンドルが必要なら、partial builds を利用できる
  • 機能比較では、Cash はモダンブラウザ対応、活発なメンテナンス、名前空間付きイベント、TypeScript コードベース、コードから生成される TypeScript 型を提供する
  • Partial builds では Cash は個別メソッドの除外が可能で、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 構成

  • $() は Cash の中核となる セレクタメソッド で、操作可能なノードコレクションを返す
  • 関数が渡された場合は、DOM の準備完了時にその関数を実行する
  • $() は selector、DOM node、nodeList、HTML string、Cash collection、document ready callback を受け取れる
  • Cash は大きく 3 種類の API を提供する
    • クエリセレクタ
    • コレクションメソッド
    • グローバル $ オブジェクトのライブラリメソッド

コレクションメソッド

  • コレクションメソッドは $() でコレクションを作成した後、$(element).addClass(className) のような形で呼び出す
  • 提供カテゴリは 属性、コレクション、CSS、データ、寸法、エフェクト、イベント、フォーム、DOM 操作、オフセット、探索に分かれる
  • 主なメソッドには次が含まれる
    • クラス/属性: 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 はコレクションのメインプロトタイプで、プラグインのようにすべてのコレクションへカスタムメソッドを追加できる

グローバル Cash メソッド

  • グローバル $ オブジェクトには型チェックとユーティリティメソッドが含まれる
  • 型チェックメソッドとして $.isArray, $.isFunction, $.isNumeric, $.isPlainObject, $.isWindow を提供する
  • ユーティリティには $.guid, $.each, $.extend, $.parseHTML, $.unique が含まれる
  • $.extend は対象オブジェクトをソースオブジェクトのプロパティで拡張し、ディープ拡張もサポートする
  • $.parseHTML は HTML 文字列からコレクションを返し、$.unique は重複を除去した新しい配列を返す

拡張と貢献

  • Cash はカスタムメソッドで拡張でき、拡張方法は extending Cash にまとめられている
  • 問題や機能要望は GitHub の issue で起票できる
  • Pull request の作業フローは、リポジトリのクローン、依存関係のインストール、npm run dev による自動再コンパイル、npm run test によるテスト実行、必要に応じた README の更新という順序になっている
  • ライセンスは MIT

1件のコメント

 
GN⁺ 2024-11-04
Hacker News の意見
  • 最近のブラウザは良くなっているので、DOM 操作を単純化するには、下の2行のエイリアスだけで十分な場合が多い
    dqs = document.querySelector.bind(document);
    dqsA = document.querySelectorAll.bind(document);
    そのため、document.querySelector('#country') の代わりに dqs('#country')document.querySelectorAll('.city') の代わりに dqsA('.city') のように使える
    残りはそのままネイティブのブラウザ関数を使っても問題なく、通常は import { dqs, dqsA } from '/lib/js/dqs.js'; のようにモジュールから取り込む
    https://github.com/no-gravity/dqs.js

    • querySelector/querySelectorAll をバインドした2行自体は有用で合理的に見えるが、それを import で取り込むのはやりすぎ
      たかが簡単な2行なので、依存関係にする理由はなく、コピーして貼り付ければよい
    • すべてのユーティリティ関数が1行で済むわけではない
      たとえば複数のイベントを扱う $.fn.one()$.fn.on() は jQuery/Cash で使うほうが簡単で、内部実装を見るとかなり多くの処理をしている: https://github.com/fabiospampinato/cash/blob/master/src/even...
    • querySelectorAll()ライブコレクションではないので、よく次のようにすぐ配列へ変換して使う
      dqsA = s => Array.from(document.querySelectorAll(s));
      こうすると結果に .map().filter() のような配列メソッドをすぐ使えるため、jQuery に近い感覚が残る
    • 最近 React 依存を取り除こうとしたが、イベント処理ではブラウザ間の違いがまだかなり大きかった
      たとえばテスト中、Safari ではある要素の select イベントがまったく発生せず、一部ブラウザではキャレットが移動しただけではイベントが出なかった
      コンポーネント、状態、props のような React の機能が不要でも、ネイティブのブラウザ関数だけでは十分ではなく、React DOM がブラウザ差を覆い隠してくれる価値が明らかになった
    • bling.js に似ているように見える: https://gist.github.com/paulirish/12fb951a8b893a454b32
  • ポリフィルをほぼすべて取り除いた後に jQuery に残る継続的な利点は、リストの自動処理だと思う
    フォーム内のすべてのボタン選択を1回の呼び出しで解除できる能力は、まだ他ではなかなか匹敵しにくく、親要素のクエリも同様
    ただし実装上の最大の問題は、リストが空だったときに黙って失敗する点
    DOM ツリーを後でレイアウト目的でリファクタリングした際に生じたこうしたバグをあまりに多く直してきたので、いま jQuery を作り直すなら、デフォルトでは空集合でエラーを出し、本当に気にしないときだけ黙って失敗するようなチェーン呼び出しやフラグを用意すると思う
    以前 Sizzle を取り除いてこのように変更できるか数時間調べたが、それ以上は進めなかった
    結局 jQuery はライブラリ対フレームワークという昔からの議論にもつながっており、巨大なフレームワークでシングルページアプリを長くやってきた分、また幻滅の谷が近づいている気がする

    • どのセレクタが実際の要素に一致するかを心配しなくてよい点が、多くの人にとって jQuery の魅力
      .foo をすべて非表示にしろと命令すれば、存在すれば非表示になり、なければ何も起きない fire and forget 方式で、CSS に似ている
      .foo { color: red; } と書いたのに文書内に .foo がなくても、小さなオーバーヘッド以外の副作用はない
    • 最新ブラウザでは、クエリ結果の全項目を変更するのも1行でかなりすっきり書ける
      document.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);
      これは反復可能な NodeList とイテレータヘルパーを活用する方法
      多くの親要素クエリは element.closest() で処理できる
    • デフォルトは厳格であるべきという点に完全に同意する
      jQuery ベースのコードでは、全開発者がプロジェクト内のすべてのセレクタを把握していて、DOM が変わるたびに更新しなければならないが、当然不可能
      jQuery の初期化関数を自前実装に置き換えて、長さチェックを強制することはできそう
  • 主流の Web サイトが文字どおりメガバイト単位の JavaScriptを配っている状況で、50KB を節約するために機能の少ないライブラリを丸ごと書き直す理由がわからない

    • このパッケージ自体とは別に、jQuery の話でこういう論理を見るたびに困惑する
      多くの記事はパッケージサイズと帯域幅の制約を理由に jQuery の使用に反対しながら、同時にずっと多くの帯域幅を使う SPA フレームワークは擁護している
      完全にばかげたカーゴカルト的な推論だ
    • その論理は「主流のファストフード店が1600カロリーの食事を売っているのに、なぜ昼食にサラダを作るのか」や「国債が35兆ドル近いのに、なぜ住宅ローン金利を比較するのか」と同じように聞こえる
      3つの場合とも答えは同じ: 大きいほうは自分ではなく、自分はもっと小さい別のことをしている
      もう1つの答えは、大きすぎることが問題なら、小さくなることが解決策に聞こえるということ
      結局聞きたいのは、開発者がなぜライブラリを書き直すことに時間を使うのか、ということのようだが、それほど驚くことではない
      プログラミングのかなりの部分は既に作られたものを作り直すことであり、仕事上必要だったり、少し違う動作や性能特性が必要だったり、単にどう動くのか学びたかったりしてそうする
    • 「なぜわざわざ?」と言うより、小さな依存関係を受け入れれば、依存関係もより小さくなり得る
    • 主流の Web サイトは広告配信用のゴミに近く、私たちがやるべきことの基準にしてはいけない
    • いまだに JavaScript 全体を50KB 未満で送ろうとしている人たちもいる
  • jQuery の代替を探しているなら、jQuery 4.0 を長く待ちすぎた末に、いくつか重要な違いを持つ独自の jQuery 風のものを作った
    アニメーション、トゥイーン、タイムラインは jQuery のカスタムシステムではなく純粋な CSS を使い、単一要素とリストを透過的に扱い、インラインで使う方向を目指している

    • ドキュメントが互いに矛盾している
      me() は要素 1 個を返す、または最初の要素か null を返すと書かれており、any() は配列または空配列を返すと書かれている
      ところが下の例では any('button')?.forEach(...)any('button')?.map(...) のように、null になり得ることを示唆している
      any() が上の説明どおり常に配列を返すのか、それとも下の例のように null もあり得るのか混乱する
    • 良い
      動作の局所性に特に関心がある
      currentScript.parentElement を使ってみた経験がどうだったのか気になる
      先月ざっと調べたときは、おそらくニッチなケースでは信頼できないという印象を受けたが、正確にいつだったかは覚えていない
      深く掘り下げたわけではなく、うまく動くようにしているのはうれしい
      asyncmodule ではないという前提なら、連続したスクリプト 3 つをロードしても、すべてのブラウザで currentScript.parentElement は依然として動くはずではないかと思う
      SvelteKit でもこの議論があり、結局対象要素を指定するためにランダム ID を実装した: https://github.com/sveltejs/kit/issues/2221
  • マイグレーションガイドを見ながら、jQuery ではできるが Cash ではできない機能のうち、知らなかったし、いつか使ってみてもよさそうなものをいくつか学んだ
    https://github.com/fabiospampinato/cash/blob/master/docs/mig...

  • 拡張目標として、TypeScript テンプレート文字列の魔法を使って要素型を正確に推論できるとよさそう
    たとえば $('div#name')HTMLDivElement だと静的に推論できる

    • そのパッケージは typed-query-selector と呼ばれている
      実際の使用例はここにある: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
    • Elixir といくつかの言語はパターンマッチングと型システムでそういうことができるが、多くの言語はそうではない
      TypeScript でそれが可能なのか分からず、どうやればできるのかもよく見えない
  • jQuery 4 が最新ブラウザ向けの jQuery 代替だと聞いた

  • 作っているブラウザ拡張では最初これを使っていたが、結局 JSX ライブラリに移った
    「簡単なアプリ」の領域を超えると、jQuery はすぐに推論しにくいコードになり、jQuery に着想を得たライブラリを自分で作った者としてもそう感じる
    結局、仕事に合った道具を使うべき
    [1]: https://github.com/aleclarson/dough
    中大規模アプリで jQuery をうまく扱えるならそれでよいが、自分の好みではない

  • 以前 JS を減らそうとしていたとき、https://github.com/filamentgroup/shoestring を使っていた
    主な理由は、本当に必要なものだけを入れる カスタムビルドを提供していたから
    Cash にも似た機能があるようだが、ドキュメントでは少し見つけにくい: https://github.com/fabiospampinato/cash/blob/master/docs/par...
    使うならまずそちらを試してみると思う
    それでも、最近のブラウザが提供しているものをそのまま使うほうが、今でもより良い選択だと思っている
    実際かなり良いし、jQuery はもはや必須ではない
    特に小さな jQuery 代替でさえ 6kB なのに、React 風ライブラリの Preact はその半分のサイズだという点を見るとなおさらだ

  • すでにある Web API に別名を付ける以上の助けになるのか、よく分からない