Skip to main content

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:

html
<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:

html
<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:

html
<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)

erb
<!-- 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

erb
<!-- 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

erb
<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)

html
<!-- templates/base.html -->
<head>
  <script type="importmap">
  {
    "imports": {
      "weblisk":  "{% static 'weblisk/weblisk.js' %}",
      "weblisk/": "{% static 'weblisk/' %}"
    }
  }
  </script>
</head>

Form with CSRF + validation

html
<!-- 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)

html
<!-- templates/layout.html -->
<head>
  <script type="importmap">
  { "imports": { "weblisk/": "/static/weblisk/" } }
  </script>
</head>

Real-time dashboard with SSE

html
<!-- 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

blade
<!-- resources/views/layouts/app.blade.php -->
<head>
  <script type="importmap">
  { "imports": { "weblisk/": "{{ asset('weblisk/') }}" } }
  </script>
</head>

Interactive component

blade
<!-- 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)

heex
<!-- lib/my_app_web/components/layouts/root.html.heex -->
<head>
  <script type="importmap">
  { "imports": { "weblisk/": "<%= ~p"/assets/weblisk/" %>" } }
  </script>
</head>

LiveView-compatible enhancement

heex
<!-- lib/my_app_web/live/chat_live.html.heex -->
<div id="chat" phx-update="stream">
  <%= for {id, message} &lt;- @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.

html
<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:

bash
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.