wcstack is a thought experiment turned into code. We imagine what future web standards could look like — reactive data binding, declarative routing, automatic component loading — and build them as if they already existed in the browser.
未来のWeb標準を想像し、それがすでにブラウザに存在するかのようにコードにする思考実験。
See it in action
Because it is HTML.
HTMLのように読める。なぜなら、HTMLだから。
<!-- Define your import maps -->
<script type="importmap">
{
"imports": {
"@components/ui/": "./components/ui/",
"@components/ui|lit/": "./components/ui-lit/"
}
}
</script>
<!-- Auto-loaded from ./components/ui/button.js -->
<ui-button></ui-button>
<!-- Auto-loaded with Lit loader -->
<ui-lit-card></ui-lit-card>
<wcs-router>
<template>
<wcs-route path="/">
<wcs-layout layout="main-layout">
<nav slot="header">
<wcs-link to="/">Home</wcs-link>
<wcs-link to="/products">Products</wcs-link>
</nav>
<wcs-route index>
<wcs-head><title>Home</title></wcs-head>
<app-home></app-home>
</wcs-route>
<wcs-route path="products">
<wcs-route path=":id(int)">
<product-detail data-bind="props"></product-detail>
</wcs-route>
</wcs-route>
</wcs-layout>
</wcs-route>
<wcs-route fallback>
<error-404></error-404>
</wcs-route>
</template>
</wcs-router>
<wcs-outlet></wcs-outlet>
<wcs-state>
<script type="module">
export default {
taxRate: 0.1,
cart: {
items: [
{ name: "Widget", price: 500, quantity: 2 },
{ name: "Gadget", price: 1200, quantity: 1 }
]
},
removeItem(event, index) {
this["cart.items"] = this["cart.items"].toSpliced(index, 1);
},
get "cart.items.*.subtotal"() {
return this["cart.items.*.price"] * this["cart.items.*.quantity"];
},
get "cart.total"() {
return this.$getAll("cart.items.*.subtotal", [])
.reduce((a, b) => a + b, 0);
},
get "cart.grandTotal"() {
return this["cart.total"] * (1 + this.taxRate);
}
};
</script>
</wcs-state>
<template data-wcs="for: cart.items">
<div>
{{ .name }} ×
<input type="number" data-wcs="value: .quantity">
= <span data-wcs="textContent: .subtotal|locale"></span>
<button data-wcs="onclick: removeItem">Delete</button>
</div>
</template>
<p>Grand Total: <span data-wcs="textContent: cart.grandTotal|locale(ja-JP)"></span></p>
Constraints
This project follows five strict constraints. They're what make it interesting.
5つの厳格な制約。これが面白さの源泉。
One <script> tag. That's it. No npm, no bundler, no config.
scriptタグ1つ。npm不要、バンドラー不要、設定不要。
Everything is a custom element. If it can't be expressed as <wcs-something>, it doesn't belong here.
すべてはカスタム要素。<wcs-something>で表現できなければ、ここには属さない。
The script just registers custom elements. No initialization code, no bootstrap ritual.
スクリプトはカスタム要素を登録するだけ。初期化コードもブートストラップ儀式も不要。
Expressions live in data-* attributes and text nodes — places HTML already allows extension. The DOM structure and semantics stay intact.
式はdata-*属性とテキストノードに配置 — HTMLが拡張を許可する場所のみ。DOM構造とセマンティクスはそのまま。
We actively adopt cutting-edge JS features. No transpiling to ES5. This is the future, after all.
最新のJS機能を積極採用。ES5へのトランスパイルなし。未来のプロジェクトだから。
These rules sound simple. They're not. Respecting HTML semantics means you need to deeply understand where the spec allows extension — and where it doesn't. Building everything as custom tags means solving lifecycle, ordering, and communication within the Custom Elements spec. No dependencies means every algorithm is yours to write. And it all has to feel like it could be a browser built-in.
シンプルに聞こえるルールだが、実際は違う。HTMLセマンティクスを尊重するなら、仕様が拡張を許す場所と許さない場所を深く理解する必要がある。すべてをカスタムタグで構築するなら、Custom Elementsの仕様の中でライフサイクル・順序・通信を解決しなければならない。依存ゼロなら、あらゆるアルゴリズムを自分で書く。そしてそのすべてが「ブラウザ組み込みかもしれない」と感じさせなければならない。
Quick Start
Pick what you need. Each tag works standalone.
必要なものだけ選ぶ。各タグは単独で動作する。
<!-- Pick one, two, or all three -->
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
<script type="module" src="https://esm.run/@wcstack/router/auto"></script>
<script type="module" src="https://esm.run/@wcstack/autoloader/auto"></script>
Each script registers its custom elements and does nothing else. No initialization, no bootstrap — tags activate when the browser parses them.
各スクリプトはカスタム要素を登録するだけ。初期化もブートストラップもなし — ブラウザがパースした時にタグが起動する。