Single File Blocks (.lego) β
Single File Blocks (SFB) let you define blocks in dedicated .lego files when using Vite as your build tool.
π New to LegoDOM?
Start with our Step-by-Step Tutorial to build a complete multi-page app with Lego Files!
Where Does My Config Go? β
The #1 question developers ask: "I have a .lego file β now where do I put my routes?"
Answer: Everything goes in your entry file (app.js or main.js):
// src/app.js β The control center of your app
import { Lego } from 'lego-dom';
import registerBlocks from 'virtual:lego-blocks'; // Plugin still uses this name for now
// 1. Register all .lego files automatically
registerBlocks();
// 2. Define your routes
Lego.route('/', 'home-page'); // home-page.lego
Lego.route('/login', 'login-page'); // login-page.lego
Lego.route('/users/:id', 'user-profile'); // user-profile.lego
// 3. Initialize global state (optional)
Lego.globals.user = null;
// 4. Start the engine
await Lego.init();Your index.html just needs:
<lego-router></lego-router>
<script type="module" src="/src/app.js"></script>That's the complete pattern! π
Why Lego Files? β
When your project grows, keeping blocks in separate files makes your codebase more organized and maintainable.
Benefits β
β
Better Organization - One file per block
β
Syntax Highlighting - Proper editor support
β
Auto-discovery - Vite plugin finds and registers blocks automatically
β
Hot Reload - Changes reflect instantly during development
β
Familiar Format - Similar to Vue SFCs if you've used them
File Format β
A .lego file contains three optional sections:
<template>
<!-- Your block markup -->
</template>
<script>
// Your block logic
export default {
// reactive state and methods
}
</script>
<style>
/* Scoped styles */
</style>Example Block β
Here's a complete example (block-user-card.lego):
<template>
<img class="avatar" src="[[ avatarUrl ]]" alt="[[ name ]]">
<h2 class="name">[[ name ]]</h2>
<p class="bio">[[ bio ]]</p>
<p>Followers: [[ followers ]]</p>
<button @click="follow()">
[[ isFollowing ? 'Unfollow' : 'Follow' ]]
</button>
</template>
<style>
self {
display: block;
padding: 1.5rem;
border: 2px solid #ddd;
border-radius: 8px;
max-width: 300px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
}
.name {
font-size: 1.5rem;
font-weight: bold;
margin: 0.5rem 0;
}
.bio {
color: #666;
margin: 0.5rem 0;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
</style>
<script>
export default {
name: 'John Doe',
bio: 'Web developer & coffee enthusiast',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=John',
followers: 1234,
isFollowing: false,
follow() {
if (this.isFollowing) {
this.followers--;
this.isFollowing = false;
} else {
this.followers++;
this.isFollowing = true;
}
}
}
</script>Vite Plugin Setup β
Installation β
npm install -D vite lego-domConfiguration β
Create or update vite.config.js:
import { defineConfig } from 'vite';
import legoPlugin from 'lego-dom/vite-plugin';
export default defineConfig({
plugins: [
legoPlugin({
blocksDir: './src/blocks', // Where to look
include: ['**/*.lego'] // What to match
})
]
});Project Structure β
my-app/
βββ src/
β βββ blocks/
β β βββ block-user-card.lego
β β βββ block-post-list.lego
β β βββ block-comment-item.lego
β βββ main.js
βββ index.html
βββ package.json
βββ vite.config.jsEntry Point β
In your src/main.js:
import { Lego } from 'lego-dom/main.js';
import registerBlocks from 'virtual:lego-blocks';
// Auto-register all discovered blocks
registerBlocks();
// Now all .lego blocks are available!Use Blocks β
In your index.html:
<!DOCTYPE html>
<html>
<head>
<title>My Lego App</title>
</head>
<body>
<div id="app">
<block-user-card></block-user-card>
<block-post-list></block-post-list>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>Block Naming β
Block names are automatically derived from filenames:
| Filename | Block Tag |
|---|---|
block-user-card.lego | <block-user-card> |
block-post-list.lego | <block-post-list> |
block-comment-item.lego | <block-comment-item> |
Naming Rules
Block names must:
- Be kebab-case (lowercase with hyphens)
- Contain at least one hyphen
- Match the pattern:
[a-z][a-z0-9]*(-[a-z0-9]+)+
β
Good: block-user-card, block-post-list, nav-menu
β Bad: UserCard, postlist, card
Section Details β
Template Section β
Contains your block's HTML markup with Lego directives:
<template>
<h1>[[ title ]]</h1>
<p b-show="showContent">[[ content ]]</p>
<ul>
<li b-for="item in items">[[ item ]]</li>
</ul>
</template>Script Section β
Exports the block's reactive state and methods:
<script>
export default {
// Reactive properties
title: 'Welcome',
count: 0,
items: ['apple', 'banana'],
// Methods
increment() {
this.count++;
},
// Lifecycle hooks
mounted() {
console.log('Block mounted!');
}
}
</script>Style Section β
Scoped styles using Shadow DOM. Use self to target the block root:
<style>
self {
display: block;
padding: 1rem;
}
h1 {
color: #333;
}
button {
background: blue;
color: white;
}
</style>Styles are automatically scoped to your blockβthey won't affect other blocks or global styles.
Dynamic Styles β
A powerful feature of LegoDOM Single File Blocks is that interpolation works inside <style> tags too!
You can use [[ ]] to bind CSS values directly to your block's state, props, or logic. Because styles are scoped (Shadow DOM), this is safe and won't leak.
<template>
<button @click="toggleTheme()">Toggle Theme</button>
</template>
<style>
/* Use state variables directly in CSS */
self {
background-color: [[ theme === 'dark' ? '#333' : '#fff' ]];
color: [[ theme === 'dark' ? '#fff' : '#000' ]];
/* You can also bind strict values for calculation */
--padding: [[ basePadding + 'px' ]];
padding: var(--padding);
}
</style>
<script>
export default {
theme: 'light',
basePadding: 20,
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
}
</script>Why this rocks π€
This eliminates the need for CSS-in-JS libraries. You get full reactivity in your CSS with standard syntax.
Performance Note
Binding to CSS properties works great for themes, settings, and layout changes.
For high-frequency updates (like drag-and-drop coordinates or 60fps animations), prefer binding to CSS Variables on the host element (style="--x: [[ x ]]") to avoid re-parsing the stylesheet on every frame.
Hot Module Replacement β
During development, changes to .lego files trigger a full page reload. Your changes appear instantly!
npm run devEdit your block, save, and see the result immediately.
Passing Props β
Pass data to blocks via the b-logic attribute:
<user-card b-logic="{
name: 'Jane Smith',
bio: 'Designer',
followers: 5678
}"></user-card>Or define defaults in the script section and override as needed. (Legacy b-data is also supported).
Best Practices β
1. Keep Blocks Small β
Each .lego file should represent a single, focused block.
β
Good: block-user-avatar.lego, block-user-name.lego
β Bad: entire-profile-page.lego
2. Use Semantic Names β
Name blocks after what they represent, not how they look.
3. Organize by Feature β
blocks/
βββ user/
β βββ block-user-card.lego
β βββ block-user-profile.lego
βββ shared/
β βββ block-app-button.legoLimitations β
.legofiles require Vite- Each file creates exactly one block
- Block name is derived from filename
Comparison: Lego File vs Traditional β
Traditional (HTML Template) β
<template b-id="my-block">
<style>self { padding: 1rem; }</style>
<h1>[[ title ]]</h1>
</template>
<my-block b-logic="{ title: 'Hello' }"></my-block>Lego File (.lego) β
<!-- my-block.lego -->
<template>
<h1>[[ title ]]</h1>
</template>
<style>
self { padding: 1rem; }
</style>
<script>
export default {
title: 'Hello'
}
</script>