Integration
Weblisk's modules are standard ES modules with zero dependencies. They work in any environment that runs JavaScript in a browser — no build step, no adapter, no framework coupling.
There are three ways to use Weblisk:
| Mode | Use case | Build step? |
|------|----------|-------------|
| CDN drop-in | Add signals/components to any existing site | No |
| Static assets | Copy lib/weblisk/ into your project | No |
| Full Weblisk | Standalone static site with islands | Optional (optimization) |
The Import Map
Every integration starts with an import map that tells the browser where Weblisk modules live:
<script type="importmap"> { "imports": { "weblisk": "/assets/weblisk/weblisk.js", "weblisk/": "/assets/weblisk/" } } </script>
Adjust the path to match where you've placed the modules. Then import what you need:
<script type="module"> import { signal, effect, component } from 'weblisk'; </script>
No npm install. No bundler. No build step.
Working Without a Server
Weblisk modules work with the file:// protocol for local development. Use relative paths in your
import map:
<script type="importmap"> { "imports": { "weblisk": "./lib/weblisk/weblisk.js", "weblisk/": "./lib/weblisk/" } } </script>
Open the HTML file directly in a browser — signals, components, and effects all work. The only features that require a server are Service Workers (browser security restriction) and cross-origin fetch requests.
Rails (Ruby on Rails)
Place the lib/weblisk/ directory in public/weblisk/ or pipe it through the asset
pipeline.
Setup (application layout)
<!-- app/views/layouts/application.html.erb --> <head> <script type="importmap"> { "imports": { "weblisk": "<%= asset_path('weblisk/weblisk.js') %>", "weblisk/": "<%= asset_path('weblisk/') %>" } } </script> </head>
Component example
<!-- app/views/orders/show.html.erb --> <wl-live-price product-id="<%= @product.id %>" interval="3000"> <span class="price">$<%= @product.price %></span> <span class="updated"><%= time_ago_in_words(@product.updated_at) %> ago</span> </wl-live-price> <script type="module"> import { component } from 'weblisk/ui/component.js'; import { fetchJSON } from 'weblisk/net/fetch.js'; component('wl-live-price', { props: { productId: '', interval: 5000 }, setup({ props, $, cleanup }) { const poll = setInterval(async () => { const data = await fetchJSON(`/api/products/${props.productId()}/price`); $('.price').textContent = `$${data.price}`; $('.updated').textContent = 'just now'; }, props.interval()); cleanup(() => clearInterval(poll)); } }); </script>
Rails renders the initial HTML with real data. Weblisk polls for updates. The API endpoint is a standard Rails controller — no special adapter needed.
Realtime with ActionCable alternative
<div id="notifications"></div> <script type="module"> import { stream } from 'weblisk/net/sse.js'; import { signal, effect } from 'weblisk'; // Connect to any SSE endpoint — Rails, Rack, or a dedicated Go service const messages = stream('/api/notifications/stream'); effect(() => { const msg = messages(); if (!msg) return; const el = document.createElement('div'); el.className = 'notification'; el.textContent = msg.data; document.getElementById('notifications').prepend(el); }); </script>
Django / Jinja2
Place lib/weblisk/ in your static files directory.
Setup (base template)
<!-- templates/base.html --> <head> <script type="importmap"> { "imports": { "weblisk": "{% static 'weblisk/weblisk.js' %}", "weblisk/": "{% static 'weblisk/' %}" } } </script> </head>
Form with CSRF + validation
<!-- templates/contact.html --> {% extends "base.html" %} {% block content %} <form id="contact-form" method="post" action="{% url 'contact' %}"> {% csrf_token %} <input type="text" name="name" required /> <input type="email" name="email" required /> <textarea name="message" required></textarea> <button type="submit">Send</button> <span id="status"></span> </form> <script type="module"> import { enhance } from 'weblisk/core/island.js'; import { formState } from 'weblisk/state/form.js'; import { sanitize } from 'weblisk/security/sanitize.js'; import { announce } from 'weblisk/a11y/aria.js'; enhance('#contact-form', (form, { $ }) => { form.addEventListener('submit', async (e) => { e.preventDefault(); const data = new FormData(form); // Sanitize user inputs before sending data.set('name', sanitize(data.get('name'))); data.set('message', sanitize(data.get('message'))); try { const res = await fetch(form.action, { method: 'POST', body: data }); if (res.ok) { $('#status').textContent = 'Message sent!'; announce('Contact form submitted successfully'); form.reset(); } } catch { $('#status').textContent = 'Failed to send. Please try again.'; } }); }); </script> {% endblock %}
Go (net/http, Gin, Echo, etc.)
Serve the lib/weblisk/ directory as static files. Your Go templates render HTML; Weblisk enhances
it client-side.
Setup (Go template)
<!-- templates/layout.html --> <head> <script type="importmap"> { "imports": { "weblisk/": "/static/weblisk/" } } </script> </head>
Real-time dashboard with SSE
<!-- templates/dashboard.html --> <div id="metrics"> <div class="metric"> <span class="label">Requests/sec</span> <span class="value" id="rps">{{.RPS}}</span> </div> <div class="metric"> <span class="label">Latency (p99)</span> <span class="value" id="latency">{{.P99}}ms</span> </div> </div> <script type="module"> import { stream } from 'weblisk/net/sse.js'; import { effect } from 'weblisk/core/signal.js'; // Go server sends SSE events at /api/metrics/stream const metrics = stream('/api/metrics/stream', { parse: JSON.parse }); effect(() => { const m = metrics(); if (!m) return; document.getElementById('rps').textContent = m.rps; document.getElementById('latency').textContent = m.p99 + 'ms'; }); </script>
The Go server pushes metrics via standard SSE. Weblisk makes it reactive with zero boilerplate.
Laravel (Blade)
Setup
<!-- resources/views/layouts/app.blade.php --> <head> <script type="importmap"> { "imports": { "weblisk/": "{{ asset('weblisk/') }}" } } </script> </head>
Interactive component
<!-- resources/views/products/show.blade.php --> <wl-cart-button product-id="{{ $product->id }}" price="{{ $product->price }}"> <button class="add">Add to Cart — ${{ $product->price }}</button> <span class="status"></span> </wl-cart-button> <script type="module"> import { component } from 'weblisk/ui/component.js'; component('wl-cart-button', { props: { productId: '', price: 0 }, setup({ props, $, effect }) { const btn = $('.add'); const status = $('.status'); btn.onclick = async () => { btn.disabled = true; const res = await fetch('/api/cart', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ product_id: props.productId(), qty: 1 }) }); if (res.ok) { status.textContent = 'Added!'; setTimeout(() => { status.textContent = ''; }, 2000); } btn.disabled = false; }; } }); </script>
Phoenix (Elixir)
Setup (root layout)
<!-- lib/my_app_web/components/layouts/root.html.heex --> <head> <script type="importmap"> { "imports": { "weblisk/": "<%= ~p"/assets/weblisk/" %>" } } </script> </head>
LiveView-compatible enhancement
<!-- lib/my_app_web/live/chat_live.html.heex --> <div id="chat" phx-update="stream"> <%= for {id, message} <- @streams.messages do %> <div id={id} class="message"><%= message.text %></div> <% end %> </div> <script type="module"> import { installScroll } from 'weblisk/nav/scroll.js'; import { announce } from 'weblisk/a11y/aria.js'; // Smooth scroll to new messages (Phoenix LiveView handles the DOM) installScroll({ container: '#chat', behavior: 'smooth' }); // Announce new messages for screen readers new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.classList?.contains('message')) { announce(`New message: ${node.textContent}`); } } } }).observe(document.getElementById('chat'), { childList: true }); </script>
Rust (Axum, Actix, Rocket)
Serve lib/weblisk/ from your static files directory. Works the same as Go — Rust renders HTML
templates, Weblisk enhances them.
<script type="importmap"> { "imports": { "weblisk/": "/static/weblisk/" } } </script> <script type="module"> import { socket } from 'weblisk/net/ws.js'; import { effect } from 'weblisk/core/signal.js'; // Connect to Rust WebSocket server const { messages, send } = socket('ws://localhost:3000/ws', { heartbeat: 30000, reconnect: true }); effect(() => { const msg = messages(); if (msg) handleMessage(JSON.parse(msg)); }); </script>
Static Site (S3 / Cloudflare / Netlify)
For pure static sites, the Weblisk CLI provides an optional optimization pass:
weblisk build
This composes layouts into flat HTML, inlines CSS, fingerprints assets, generates sitemap and RSS, and
minifies. The output is a dist/ folder you deploy anywhere.
But this step is optional. Without it, the site still works — the dev server handles composition on-the-fly, or you can compose manually.
What The Server Doesn't Need To Know
Weblisk never requires anything from your server beyond serving files. The communication bridge uses standard protocols:
| Protocol | Weblisk Module | Server Requirement |
|----------|---------------|-------------------|
| HTTP | net/fetch.js | Any REST endpoint |
| SSE | net/sse.js | Any SSE endpoint |
| WebSocket | net/ws.js | Any WS endpoint |
| WebTransport | net/transport.js | HTTP/3 + QUIC |
Your server can be Go, Rust, Python, Ruby, Java, PHP, C#, or anything that speaks HTTP. Weblisk doesn't care — it speaks to the standards, not the implementation.