保守する人のためのライブラリです。 書き直す予算はなくても、事故だけは止められます。
<script src="https://cdn.jsdelivr.net/npm/bridgey@2"></script>これだけ。ビルド不要、依存ゼロ、gzip 18kB、eval 不使用(CSP セーフ)。
HTML は1文字も変えなくていいし、
いま動いている jQuery のコードも1行も変えなくていい。
demo.html という名前で保存して、ブラウザで開くだけです。サーバもビルドも要りません。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<p>合計 <span id="total">0</span> 円</p>
<button id="add">追加する</button>
<script src="https://cdn.jsdelivr.net/npm/bridgey@2"></script>
<script>
const count = $$.state(0);
const total = $$.computed(() => count() * 1100);
$$("#total").text(total, $$.money); // 状態が変われば画面が勝手に追いつく
$$("#add").click(() => count(n => n + 1)); // ここは jQuery のまま
</script>
</body>
</html>$ を $$ にするだけ。増えた概念は1つだけです。
$$("#total").text("1,100"); // 値を渡す → その瞬間だけ入る(jQuery と同じ)
$$("#total").text(total); // state を渡す → 以後ずっと自動で追いかける ← これだけが新しいstate 状態を作る
computed 状態から計算する(合計・小計・表示するか)
when 条件で表示を出し入れする
repeat 一覧を描く(行の DOM を使い回すので入力が消えない)
これで8割書けます。残りは知らなくても詰まりません(必要になった場面で bridgey が教えます)。
const count = $$.state(0);
count() // 読む ← .val() と同じ手癖
count(5) // 書く ← .val(5) と同じ手癖
count(n => n + 1) // 更新
`${count}` // 補間もできる実際の現場で起きていた事故を、そのまま止めます。
| 事故 | bridgey では |
|---|---|
| 数値計算が合わない(change の中で合計を計算して代入していた) | 合計は導出値。代入する場所が存在しないので更新漏れが起きない |
| 非表示の必須項目で送信できず、理由も出ない(CV が消える) | 見えているフィールドだけを検証する。エラー0件で送信不能にならない |
| オートフィルで入力しても未入力扱い | value の代入を検知する(既存 jQuery の .val() 経由でも追従) |
HTML に要素を1つ挟むと静かに壊れる(next() / children() / eq()) |
名前で引く。壊れたら0件で警告する(jQuery は黙る) |
1箇所の undefined で全機能が死ぬ(common.js の $(function(){})) |
component() 単位でエラーを隔離。壊れた部品だけが止まる |
| class 名のタイポで、隠すべきものが見えてしまう | CSS に無い class 名を検出し、似た名前を提示する |
| Ajax で追加した要素にプラグインが効かない | component() が自動で適用。消えたら自動で片付ける |
| 二重送信でレコードが2件できる | action() / form() が構造的に防ぐ |
リストを .html() で作り直して入力が消える |
repeat() がキー付き差分。行の DOM を使い回す |
| 通信の後着追い越しで古い金額が残る | resource() が前のリクエストを中断し、古い応答を捨てる |
**1機能だけ置き換えられます。**予算が取れない現場のために、これが最優先の設計です。
第1週 金額計算だけ bridgey に(20行)。他は jQuery のまま。同じ DOM に共存OK
第2週 フォームの検証だけ移す。CV 事故が止まる
第3週 タブとアコーディオンを component に。undefined の全滅が止まる
…残りは触らなくてよい
同じ UI を4段階で書けます。①②③はいま全部動きます。
// ① いまのやり方(そのまま動く)
$(".tab").on("click", function () {
$(".tab").removeClass("active");
$(this).addClass("active");
});
// ② bridgey の JS API(事故が減る)
const current = $$.state("a");
$$(".tab").each((el) => $$(el)
.click(() => current(el.dataset.tab))
.selected(() => current() === el.dataset.tab));
// ③ ディレクティブ(宣言的な考え方が身につく・完全に任意)
// <li data-brg-selected="isCurrent" data-brg-on-click="pick">
$$.bind("#tabs", { isCurrent, pick });
// ④ Svelte / Vue(卒業)
// <li class:active={isCurrent} on:click={pick}>
// <li :class="{active: isCurrent}" @click="pick">③の記法は④と1対1で対応しています。
| bridgey | Vue | Svelte |
|---|---|---|
data-brg-text="total" |
{{ total }} |
{total} |
data-brg-when="isFree" |
v-if="isFree" |
{#if isFree} |
data-brg-repeat="todos" |
v-for="t in todos" |
{#each todos as t} |
data-brg-class-active="isOn" |
:class="{active: isOn}" |
class:active={isOn} |
data-brg-attr-src="url" |
:src="url" |
src={url} |
data-brg-value="keyword" |
v-model="keyword" |
bind:value={keyword} |
data-brg-on-click="save" |
@click="save" |
on:click={save} |
ディレクティブに式は書けません。
<p data-brg-when="qty > 3"> <!-- ✗ 書けない(警告が出ます) -->const isFree = $$.computed(() => qty() >= 3); // ロジックは JS 側<p data-brg-when="isFree"> <!-- ✓ -->式を評価しないので new Function が不要 = CSP セーフのまま。{{ }} を作らないので Blade / Twig / Smarty と衝突しません。
text html val attr removeAttr prop css addClass removeClass toggleClass hasClass data
find closest parent children first last is each append empty remove
on off click change submit input keydown focus … $$.fn(プラグイン拡張口)
そこに state を渡せるようになっただけです。
$$("#label").text(total); // 表示
$$("#btn").prop("disabled", busy); // 活性
$$("#box").toggleClass("open", isOpen); // 見た目
$$("#img").attr("src", url); // 属性
$$("#q").val(keyword); // 双方向
$$("#bar").css("width", () => `${pct()}%`); // 関数を渡せば依存を自動追跡表示の形はここで整えられます。
$$("#n").text(count, "件"); // 3件 後ろに付ける
$$("#n").text(count, "残り{}件"); // 残り3件 {} の位置に入れる
$$("#t").text(total, $$.money, "円"); // 23,800円 3桁区切り+単位$$("#signup").form({ … }) // フォームの事故を止める(非表示・オートフィル・二重送信)
$$("#plan input").group() // ラジオ/チェックボックスの選択を1つの状態に
$$.component("tabs", fn) // common.js を安全にする(要素がある頁でだけ動く・エラー隔離)
$$.resource(src, fetcher) // 通信の競合を止める(自動中断・後着追い越しなし)mode vars toggleAttr selected expanded invalid busy disabled
action scope effect tick batch untrack bind money
.addClass("active") と書くと、bridgey がこう教えます。
[bridgey] addClass("active") で状態を表しているようです。
→ .selected(state) が使えます: aria-selected も一緒に付き、
他の要素から消し忘れることもありません
(このヒントは $$.hints = false で止められます)
id も class も今まで通り使えます。@ は data-brg 属性の省略記法です。
$$("#total") // id="total"
$$(".total") // class="total"
$$("@total") // data-brg="total" ← [data-brg="total"] の略記@ を使うと、class 名や DOM 構造が変わっても JS が壊れません(デザイナーが自由に見た目を変えられる)。必須ではありません。0件になったときは、こう教えます。
[bridgey] $$(".price") が0件でした。text() を結びつけられません。
HTML が変わった可能性があります(class 名の変更・要素の削除・読み込み順)。
→ 壊れにくくするには data 属性で結びつけてください:
<span data-brg="price"></span> → $$("@price")
属性名は $$.attr = "data-ui" で変えられます。
既定は $$。<script> タグの1箇所だけで変えられます。
<script src="…/bridgey@2"></script> <!-- $$ -->
<script src="…/bridgey@2" data-global="$"></script> <!-- $ -->
<script src="…/bridgey@2" data-global="$$$"></script> <!-- $$$ -->
<script src="…/bridgey@2" data-global="$_$"></script> <!-- $_$ -->
<script src="…/bridgey@2" data-global="brg,$$"></script> <!-- 両方 -->$$ が既に使われていたら(Prototype.js など)、勝手に別名へ逃がさず警告します。サイトによって名前が変わると、コピーしたコードが動かない原因が分からなくなるからです。window.bridgey からは常に使えます。
npm i bridgeyimport { $$ } from "bridgey";
import { $$ as $ } from "bridgey"; // 好きな名前に(言語機能で足りる)TypeScript で書かれているので、型定義は実装と乖離しません。
$$("input#name").prop("checked", flag); // OK
const count = $$("#n").state(0);
count("あ"); // ❌ number ではない| 持たないもの | 代わりに |
|---|---|
fadeIn / slideToggle |
CSS の transition + .toggleClass("is-open", state) |
$.ajax / $.get / $.post |
fetch()。競合制御が要るときは $$.resource() |
$.extend / $.each / $.map |
{...a, ...b} / for...of / Array#map |
serialize() |
new FormData(form)。検証は form() |
next / prev / siblings / eq / index |
closest("@row").find("@price")(構造ではなく名前で引く) |
width / height / offset |
getBoundingClientRect() |
show / hide / toggle |
when(state) |
| ルーター / SPA 化 | URL の主権はサーバーに |
| スコープ付き CSS / 仮想DOM / SSR | 入れません |
呼ぶと、代わりの書き方を教えて例外になります。
$$("#row").next();
// Error: [bridgey] next() は使えません。HTML の構造が変わると壊れるため v2 では
// 提供していません(要素を1つ挟むだけで、例外も出ないまま静かに動かなくなります)。
// → 構造ではなく名前で引いてください: $$(this).closest("@row").find("@price")npm install
npm run buildあとは demo/index.html をブラウザで開くだけです(サーバー不要)。
start demo/index.html # Windows
open demo/index.html # macOSデモでは、実際に作者がレガシーな現場で遭遇した事故を再現して止めてみせます。
- 「HTML を1段ラップする」 → 従来のやり方(
next())だけが静かに壊れる - 「オートフィルを再現」 → イベントが飛ばなくても検証が追従する
- 「送信を5連打」 → 1回しか送信されない
- 「式を書いてみる」 → ディレクティブが警告を出す
- わざと壊した component → 他の部品は動き続ける
- 同じ画面を ②JS API と ③ディレクティブで並べて比較
コンソールを開いておくと、警告とヒントが読めます。
- モダンブラウザ(ES2020 /
AbortController/MutationObserver) eval/new Functionを使いません →script-src 'self'の厳格な CSP でも動きます- jQuery と同じページ・同じ要素で共存できます(
$は奪いません) - jQuery プラグイン(slick / DataTables 等)は bridgey では動きません。jQuery 本体と併用してください
v2 は破壊的変更です。v1 は 1.x ブランチで凍結しています。
| v1 | v2 |
|---|---|
useEngine(svelteEngine) / mount(Component) |
削除。Svelte / Vue への依存をやめました |
state(0) → count.value |
$$.state(0) → count() / count(5) |
bindText / bindClass / bindAttr / bindValue |
.text(state) / .toggleClass(n, state) / .attr(n, state) / .val(state) |
computed(deps, fn) |
$$.computed(fn)(依存は自動追跡) |
.val() が String のサブクラス |
普通の文字列(=== "" が期待通り動く) |
| jQuery 共存は非サポート | 公式サポート |
npm test # jsdom で DOM も dist も検査
npm run typecheck # tsc(型検査 + .d.ts 生成)
npm run build # esbuild で dist を作るテストは「事故の名前」で書いてあります。
✔ ★①見えていないフィールドは検証しない
✔ ★②JS からの値の代入も検知する(オートフィル・既存 jQuery の .val() 相当)
✔ ★行の DOM は使い回される(入力中の文字が消えない)
✔ ★後着追い越しが起きない(遅い応答が後で届いても上書きしない)
✔ ★1つの初期化が失敗しても、他の部品は動き続ける
✔ ★数値計算が合わない: 合計を「書き込む場所」を無くす
✔ dist: eval / new Function を使っていない(CSPセーフ)
bridgey — modern state management with jQuery's ergonomics. Zero dependencies, no build step, ~18kB gzipped, and no eval (works under a strict CSP).
Built for people who maintain legacy jQuery rather than start new apps: adopt it one feature at a time, keep jQuery on the same DOM node, and never edit your HTML if you can't. Where jQuery fails silently, bridgey tells you what broke — empty selector matches, class names missing from your CSS, duplicate list keys, components that failed to initialize.
Four words to start: state, computed, when, repeat. Optional directives (data-brg-text, data-brg-when, …) map one-to-one onto Vue and Svelte template syntax, so this becomes a stepping stone rather than a dead end.
<script src="https://cdn.jsdelivr.net/npm/bridgey@2"></script>
<script>
const count = $$.state(0);
$$("#total").text($$.computed(() => count() * 1100), $$.money);
$$("#add").click(() => count(n => n + 1));
</script>MIT License