Project Structure
A Weblisk project has a clean, flat structure:
my-app/ ├── index.html ← Home page (full standalone HTML) ├── about.html ← About page ├── 404.html ← Not found page ├── docs/ │ └── index.html ← Docs landing ├── js/ │ └── islands/ ← Interactive islands │ └── shell.js ← Shell bootstrapper ├── css/ │ └── styles.css ← App styles ├── images/ ← Image assets (SVGs, favicons) ├── sw.js ← Service Worker └── .env ← Environment config
Pages
Every page is a full standalone HTML document — <!DOCTYPE html>,
<head>, <body>, everything. What you write is what ships. No composition, no layout
cascade, no build-time transformation.
Each file in the project root maps directly to a URL path:
| File | URL |
|------|-----|
| index.html | /index.html |
| about.html | /about.html |
| blog.html | /blog.html |
| docs/index.html | /docs/index.html |
Your web server can map cleaner URLs (e.g. /about → /about.html) if desired —
that's a server concern, not a framework concern.
Page Template
Every page includes its own import map and shell script:
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Page</title> <meta name="description" content="Page description."> <link rel="stylesheet" href="/css/styles.css"> <script type="importmap"> { "imports": { "weblisk": "/lib/weblisk/weblisk.js", "weblisk/": "/lib/weblisk/" } } </script> </head> <body> <!-- your content --> <script type="module" src="/js/islands/shell.js"></script> </body> </html>
Islands
Islands are JavaScript files that enhance static HTML using data-island and
data-hydrate attributes. They live in js/islands/ by convention.
An island script exports a default function that receives the DOM element:
// js/islands/counter.js import { signal, effect } from 'weblisk'; export default function counter(el) { const [count, setCount] = signal(0); const btn = el.querySelector('button'); btn.addEventListener('click', () => setCount(count() + 1)); effect(() => { btn.textContent = 'Count: ' + count(); }); }
Use it in your HTML:
<div data-island="/js/islands/counter.js" data-hydrate="idle"> <button>Count: 0</button> </div>
The data-hydrate attribute controls when the island loads: load, idle,
visible, media, event, or never.
.env
Environment configuration (loaded automatically by the CLI):
WL_ORIGIN=http://localhost:5500 WL_PORT=5500 WL_DIST=dist
Blueprint-Driven Layout
When using the starter template, the project structure is declaration-first — YAML files describe the site, and code is generated:
my-site/ ├── global.yaml ← Project identity, navigation, SEO ├── code.yaml ← Code conventions, formatting rules ├── theme.yaml ← Design tokens (colors, typography, spacing) ├── pages/ ← Page declarations (YAML) │ ├── index.yaml │ ├── features.yaml │ └── docs.yaml ├── components/ ← Component declarations (YAML) │ ├── hero.yaml │ └── pricing-card.yaml ├── weblisk.json ← Project config ├── .env ← Config └── dist/ ← Generated output (build artifact)
Run weblisk generate to produce the full site from these declarations. See Project Standards for details.