npm package: @malv/rendering
Description: Build-time HTML component rendering engine for Malv
Author: octamap
Latest version: 1.2.0

# @malv/rendering

Build-time HTML component rendering engine for Vite. Create reusable HTML components with scoped CSS and slots - no runtime JavaScript required for rendering.

## Installation

```bash
npm install @malv/rendering
```

## Quick Start

### 1. Create a component

```html
<!-- components/my-button.html -->
<button class="my-button">
    <slot></slot>
</button>

<style>
.my-button {
    background: blue;
    color: white;
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
}
</style>
```

### 2. Configure Vite

```typescript
// vite.config.ts
import { Rendering } from '@malv/rendering'

export default {
    plugins: [
        Rendering({
            'my-button': './components/my-button.html',
        })
    ]
}
```

### 3. Use in HTML

```html
<!-- index.html -->
<my-button>Click me</my-button>
```

At build time, this becomes:

```html
<button class="my-button">Click me</button>
```

## API

### `Rendering(components, options?)`

The main Vite plugin.

```typescript
import { Rendering } from '@malv/rendering'

Rendering(components: ComponentsMap, options?: RenderingOptions)
```

### `folder(path)`

Register all `.html` files in a directory as components. File names become tag names.

```typescript
import { Rendering, folder } from '@malv/rendering'

export default {
    plugins: [
        Rendering({
            ...folder('./components'),
            // components/my-button.html → <my-button>
            // components/my-card.html → <my-card>
        })
    ]
}
```

### `component(name, source)`

Register a single component.

```typescript
import { Rendering, component } from '@malv/rendering'

export default {
    plugins: [
        Rendering({
            ...component('my-button', './components/my-button.html'),
        })
    ]
}
```

### `components(map)`

Register multiple components at once.

```typescript
import { Rendering, components } from '@malv/rendering'

export default {
    plugins: [
        Rendering({
            ...components({
                'my-button': './components/my-button.html',
                'my-card': './components/my-card.html',
            }),
        })
    ]
}
```

## Component Sources

Components can be defined in several ways:

```typescript
{
    // Relative file path
    'my-button': './components/my-button.html',
    
    // Absolute file path
    'my-card': { type: 'absolute', path: '/path/to/my-card.html' },
    
    // Raw HTML content
    'my-divider': { type: 'raw', html: '<hr class="divider" />' },
    
    // Dynamic import
    'my-dynamic': () => import('./components/my-dynamic.html'),
}
```

## Features

### Slots

Use `<slot>` to insert content passed to your component.

```html
<!-- components/card.html -->
<div class="card">
    <slot></slot>
</div>
```

```html
<!-- Usage -->
<card>
    <p>This content goes in the slot</p>
</card>
```

### Named Slots

Use `#slotname` to create named slots.

```html
<!-- components/card.html -->
<div class="card">
    <div class="card-header">
        <slot name="header"></slot>
    </div>
    <div class="card-body">
        <slot></slot>
    </div>
</div>
```

```html
<!-- Usage -->
<card>
    <h2 #header>Card Title</h2>
    <p>Card content goes here</p>
</card>
```

### Attributes

Attributes are passed to the `#default` element (or root element if none specified).

```html
<!-- components/button.html -->
<button #default class="btn">
    <slot></slot>
</button>
```

```html
<!-- Usage -->
<button disabled data-action="submit">Submit</button>

<!-- Result -->
<button class="btn" disabled data-action="submit">Submit</button>
```

### Scoped Styles

Each component's `<style>` block is extracted and bundled. In development, styles update via HMR without page refresh.

```html
<!-- components/button.html -->
<button class="btn"><slot></slot></button>

<style>
.btn {
    background: blue;
    color: white;
}
</style>
```

### Component Scripts

Add `<script>` blocks for component-specific JavaScript.

```html
<!-- components/counter.html -->
<div class="counter">
    <button class="decrement">-</button>
    <span class="count">0</span>
    <button class="increment">+</button>
</div>

<script>
document.querySelectorAll('.counter').forEach(counter => {
    const count = counter.querySelector('.count')
    counter.querySelector('.increment').onclick = () => {
        count.textContent = parseInt(count.textContent) + 1
    }
    counter.querySelector('.decrement').onclick = () => {
        count.textContent = parseInt(count.textContent) - 1
    }
})
</script>
```

## Plugins

Create plugins to dynamically generate components from external data sources.

```typescript
import { Rendering, type RenderingPlugin } from '@malv/rendering'
import fs from 'fs/promises'

const dataPlugin: RenderingPlugin = {
    name: 'product-components',
    filePaths: ['./data/products.json'],  // Changes trigger full reload
    
    async components() {
        const products = JSON.parse(
            await fs.readFile('./data/products.json', 'utf-8')
        )
        
        return products.map(product => ({
            name: `product-${product.id}`,
            filePaths: [`./data/products/${product.id}.json`],  // Changes trigger component reload
            
            async resolve() {
                const data = JSON.parse(
                    await fs.readFile(`./data/products/${product.id}.json`, 'utf-8')
                )
                return `
                    <div class="product">
                        <h2>${data.name}</h2>
                        <p>${data.price}</p>
                    </div>
                `
            }
        }))
    }
}

export default {
    plugins: [
        Rendering({}, { plugins: [dataPlugin] })
    ]
}
```

## Options

```typescript
interface RenderingOptions {
    plugins?: RenderingPlugin[]
}
```

## How It Works

1. **Initialization**: Components are loaded and their CSS/JS is extracted
2. **Transformation**: HTML files are processed, replacing component tags with their content
3. **Slot Resolution**: Content is inserted into `<slot>` elements
4. **Attribute Mapping**: Attributes are applied to designated elements
5. **CSS Bundling**: Extracted styles are bundled into a single CSS file
6. **HMR (Dev)**: Changes trigger granular updates without full page reload

## Output

In production builds:

```
dist/
├── index.html           # Processed HTML with components expanded
├── mesa-styles/
│   └── indexAb3Cd-mesa.css   # Bundled component CSS
└── assets/
    └── ...              # Other assets
```

## License

MIT