This is a minimal, step-by-step setup for a new Atomica site using Vite and TypeScript.
If you do not have pnpm:
npm install -g pnpm
pnpm create vite my-atomica-site --template vanilla-ts
cd my-atomica-site
pnpm add atomica
Edit index.html:
<div id="app"></div>
Create or edit src/main.ts:
import { h, mount, signal } from 'atomica';
const count = signal(0);
const App = () =>
h('button', { onClick: () => count.set((c) => c + 1) }, () => `Count: ${count.get()}`);
const root = document.getElementById('app');
if (root) {
mount(h(App, {}), root);
}
pnpm dev
Here is a minimal layout with a few TypeScript modules and a CSS file:
my-atomica-site/
index.html
tsconfig.json
vite.config.ts
src/
main.ts
widgets/
CounterWidget.ts
pages/
home.ts
style.css
Example index.html (source):
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
Example src/main.ts:
import { h, mount } from 'atomica';
import { Home } from './pages/home';
import './style.css';
const root = document.getElementById('app');
if (root) mount(h(Home, {}), root);
Example src/pages/home.ts:
import { h } from 'atomica';
import { CounterWidget } from '../widgets/CounterWidget';
export const Home = () =>
h('main', null,
h('h1', null, 'Welcome'),
h(CounterWidget, {})
);
Example src/widgets/CounterWidget.ts:
import { h, signal } from 'atomica';
export const CounterWidget = () => {
const count = signal(0);
return h('button', { onClick: () => count.set((c) => c + 1) }, () => `Count: ${count.get()}`);
};
Example src/style.css:
body { font-family: system-ui, sans-serif; }
main { padding: 2rem; }
If you want JSX, add to tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "atomica/dom"
}
}
JSX compiles to h(...); the mental model stays the same.
Vite builds a static folder, usually dist/, containing plain HTML/CSS/JS assets:
dist/
index.html
assets/
index-7f3c1c2a.js
index-3a8b9d7e.css
The generated dist/index.html will look like this (hashed filenames are normal):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Atomica Site</title>
<link rel="stylesheet" href="/assets/index-3a8b9d7e.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/assets/index-7f3c1c2a.js"></script>
</body>
</html>
You can deploy dist/ to any static host or CDN.
Hashed filenames are the default because they are safe for long-term caching. If you want stable names, configure Vite’s Rollup output:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/app.js',
chunkFileNames: 'assets/chunk-[name].js',
assetFileNames: 'assets/[name][extname]'
}
}
}
});
For multiple entry points, add multiple HTML files in your source. Each HTML file references its own entry module:
<!-- index.html -->
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<!-- admin.html -->
<div id="admin"></div>
<script type="module" src="/src/admin.ts"></script>
Vite will build both pages into dist/ with separate bundles and replace the script tags with built asset paths.
Vite supports preprocessors out of the box. Install what you need and import the file.
Sass/SCSS:
pnpm add -D sass
import './style.scss';
Less:
pnpm add -D less
import './style.less';
PostCSS (autoprefixer example):
pnpm add -D postcss autoprefixer
// postcss.config.js
export default {
plugins: {
autoprefixer: {}
}
};
CSS Modules:
import styles from './widget.module.css';
h('button', { class: styles.button }, 'Click');