This package is experimental. It may not be suitable for production use at this time, as it is subject to bugs and breaking changes.(invalid icon)
npm install thunderous-server
npm init thunderous .html <slot> <script expr> <script server> <script isomorphic> <script> <script type="importmap"> about.html /about blog/post-1.html /blog/post-1
src/
├── index.html # → /
├── about.html # → /about
├── contact.html # → /contact
└── blog/
├── index.html # → /blog
└── hello-world.html # → /blog/hello-world
_ <?layout>
<html>
<head>
<title>My Site</title>
</head>
<body>
<header>Navigation here</header>
<main>
<slot><!-- Page content appears here --></slot>
</main>
<footer>© 2024</footer>
</body>
</html>
_layout.htmlWhy processing instructions?(invalid icon)
<script expr>
<ul>
<script expr>
items.map((item) => html`<li>${item.name}</li>`);
</script>
</ul>
<script server> html <script server>
<script server>
export default {
title: 'My Page',
items: ['Apple', 'Banana', 'Cherry'],
fetchData: async () => {
// Fetch from APIs, read files, etc.
return { message: 'Hello from server!' };
},
};
</script>
<h1>
<script expr>
title;
</script>
</h1>
<script isomorphic>
<script isomorphic>
import { MyComponent } from './components/my-element';
MyComponent.define('my-element');
</script>
<!-- Renders as declarative shadow DOM on the server -->
<my-element name="Alice"></my-element>
<script type="module">
<script type="module">
import { format } from 'date-fns';
console.log(format(new Date(), 'yyyy-MM-dd'));
</script>
<!-- thunderous-server vendorizes 'date-fns' automatically -->
date-fns .ts .js
<script isomorphic src="./components/counter.ts"></script>
thunderous.config.ts
// thunderous.config.ts
export default {
name: 'My App',
baseDir: 'src', // Where your HTML files live
outDir: 'dist', // Build output directory
};
import { build, dev } from 'thunderous-server';
// Development server
await dev(); // Starts Vite dev server on port 3000
// Production build
build(); // Generates static site in outDir
import { escapeHtml, raw, getMeta, setMeta } from 'thunderous-server';
// Escape HTML entities
escapeHtml('<script>alert("xss")</script>'); // <script>...
// Bypass escaping with raw()
html`<div>${raw('<em>Trusted HTML</em>')}</div>`;
// Set page metadata (affects title, breadcrumbs, etc.)
setMeta({ title: 'My Page', breadcrumbs: [{ name: 'Home', path: '/' }] });
<script server>
export default {
products: [
{ id: 1, name: 'Widget', price: 19.99 },
{ id: 2, name: 'Gadget', price: 29.99 },
]
};
</script>
<div class="product-list">
<script expr>
products.map(p => html`
<article data-id="${p.id}">
<h3>${p.name}</h3>
<p>$${p.price.toFixed(2)}</p>
</article>
`);
</script>
</div>
<script server>
export default { isLoggedIn: true, username: 'Alice' };
</script>
<nav>
<script expr>
isLoggedIn
? html`<span>Welcome, ${username}!</span>`
: html`<a href="/login">Sign In</a>`;
</script>
</nav>
<script isomorphic>
import { UserCard } from './components/user-card';
UserCard.define('user-card');
</script>
<!-- This renders as declarative shadow DOM on the server -->
<user-card name="Alice" role="Admin"></user-card>
<script server>
<!-- BAD: Only the last default export wins -->
<script server>
export default { a: 1 };
</script>
<script server>
export default { b: 2 }; // This overwrites the first!
</script>
<!-- GOOD: Consolidate into a single block -->
<script server>
export default { a: 1, b: 2 };
</script>
<script server> <script server> <script isomorphic>(invalid icon)
<!-- BAD: document doesn't exist on the server -->
<script server>
export default {
element: document.getElementById('app'), // ❌ Will error
};
</script>
<!-- BAD: isomorphic scripts do run on the client, but they also
run on the server where the problem occurs. -->
<script isomorphic>
const app = document.getElementById('app'); // ❌ Will error
</script>
typeof window !== 'undefined' <script>(invalid icon)
clientOnlyCallback() html
<!-- BAD: Returns raw string, not trusted HTML -->
<script expr>
'<div>' + content + '</div>'; // Will be escaped!
</script>
<!-- GOOD: -->
<script expr>
html`<div>${content}</div>`; // Properly handled
</script>
thunderous dev thunderous build(invalid icon)
calc() clamp() color-mix() type="module" isomorphic <script expr> [object Object] html