Renderização Básica
Função h() e html (HTM Template Tag)
Section titled “Função h() e html (HTM Template Tag)”Slash oferece duas formas de criar elementos DOM: a função h() (hyperscript) e o template tag html (HTM).
HTM Template Tag (Recomendado)
Section titled “HTM Template Tag (Recomendado)”O template tag html permite escrever markup HTML-like diretamente no JavaScript/TypeScript sem necessidade de transpilação:
import { html } from '@_bashell/slash'
const element = html` <div class="container"> <h1>Hello, World!</h1> <p>This is a paragraph</p> </div>`Vantagens:
- Sintaxe familiar (similar a JSX)
- Sem build step necessário
- Type safety completo
- Syntax highlighting em editores modernos
Função h() (Hyperscript)
Section titled “Função h() (Hyperscript)”Para uma abordagem mais programática, use a função h():
import { h } from '@_bashell/slash'
const element = h('div', { class: 'container' }, h('h1', null, 'Hello, World!'), h('p', null, 'This is a paragraph'))Assinatura:
function h( tag: string | Component, props: Props | null, ...children: Child[]): NodeComparação: html vs h()
Section titled “Comparação: html vs h()”// HTMconst greeting = html` <div class="greeting"> <h1>Hello, ${name}!</h1> </div>`
// Hyperscript equivalenteconst greeting = h('div', { class: 'greeting' }, h('h1', null, `Hello, ${name}!`))Criando Elementos DOM
Section titled “Criando Elementos DOM”Elementos HTML Básicos
Section titled “Elementos HTML Básicos”import { html } from '@_bashell/slash'
// Div simplesconst div = html`<div>Content</div>`
// Com classesconst styled = html`<div class="container flex">Styled</div>`
// Com IDsconst unique = html`<div id="app">App Root</div>`
// Elementos vaziosconst input = html`<input type="text" />`const br = html`<br />`Elementos SVG
Section titled “Elementos SVG”Slash detecta automaticamente elementos SVG e aplica o namespace correto:
import { html } from '@_bashell/slash'
const icon = html` <svg width="24" height="24" viewBox="0 0 24 24"> <circle cx="12" cy="12" r="10" fill="blue" /> <path d="M12 6v6l4 2" stroke="white" stroke-width="2" /> </svg>`Tags SVG reconhecidas automaticamente: src/utils/constants.ts
svg,circle,path,rect,line,polyline,polygonellipse,text,g,defs,use,symbol,clipPathlinearGradient,radialGradient,stop,mask,pattern
Props e Children
Section titled “Props e Children”Props (Propriedades)
Section titled “Props (Propriedades)”Props são passadas como objeto no segundo argumento de h() ou como atributos no html:
// Com htmlconst button = html` <button class="btn btn-primary" disabled=${false} data-id="123" > Click Me </button>`
// Com h()const button = h('button', { class: 'btn btn-primary', disabled: false, 'data-id': '123'}, 'Click Me')Tipos de Props
Section titled “Tipos de Props”1. Atributos HTML
Section titled “1. Atributos HTML”const input = html` <input type="text" placeholder="Enter name" required=${true} maxlength="50" />`2. Properties JavaScript
Section titled “2. Properties JavaScript”Propriedades especiais são definidas via assignment direto:
const input = html` <input type="checkbox" checked=${true} value="option1" />`Properties especiais:
value(inputs, textarea, select)checked(checkbox, radio)selected(option)disabledclassName(alternativa aclass)
3. Classes
Section titled “3. Classes”Classes podem ser strings, arrays ou objetos:
// String simplesconst el1 = html`<div class="container"></div>`
// Array (classes condicionais)const classes = ['btn', isActive && 'active', 'large']const el2 = html`<button class=${classes}>Click</button>`
// Objeto (toggle classes)const classMap = { active: true, disabled: false, large: true }const el3 = html`<button class=${classMap}>Click</button>`Implementação: src/rendering/props-core.ts
4. Estilos
Section titled “4. Estilos”// String CSSconst el1 = html`<div style="color: red; font-size: 16px"></div>`
// Objeto CSSconst styles = { color: 'red', fontSize: '16px' }const el2 = html`<div style=${styles}></div>`Cada declaração passa por uma política de CSS: declarações inseguras (url(javascript:...), expression(...), comentários /* */, barra invertida fora de aspas, url() sem aspas com caracteres fora do conjunto seguro) são descartadas e as demais são mantidas. Um style com mais de 8 KB é descartado, e um que fica vazio é omitido. Veja Segurança.
Children (Filhos)
Section titled “Children (Filhos)”Children podem ser:
- Strings e números
- Elementos DOM (Node)
- Arrays (aninhados)
- Objetos
Reactive<T>(comget()esubscribe(), comoRouter({ router })), mantidos em sincronia viasubscribe - Valores lidos de um
Statecomstate.get(). Passar o próprioStatecomo child (${count}) não é reativo: o componente que lêget()re-renderiza quando o state muda null,undefined,false(ignorados)
import { html, createState } from '@_bashell/slash'
const count = createState(0)const name = "Alice"
// Monte como <${Page} /> para que a leitura de count.get() seja reativaconst Page = () => html` <div> <h1>Hello, ${name}!</h1> <p>Count: ${count.get()}</p> ${count.get() > 5 && html`<p>Count is high!</p>`} <ul> ${[1, 2, 3].map(n => html`<li>Item ${n}</li>`)} </ul> </div>`Strings são sempre texto
Section titled “Strings são sempre texto”Uma string nunca vira HTML, seja qual for o conteúdo. Isso protege contra XSS e vale para qualquer valor, inclusive os que vêm do usuário:
const comentario = '<img src=x onerror="alert(1)">'
html`<p>${comentario}</p>` // mostra o texto literal; nada executaPara inserir marcação confiável que você mesmo gerou, use unsafeHtml(...) como filho. Ele não sanitiza: nunca passe dado de usuário por ele.
import { html, unsafeHtml } from '@_bashell/slash/core'
html`<button>${unsafeHtml('<svg viewBox="0 0 8 8"><circle cx="4" cy="4" r="3"/></svg>')} Salvar</button>`Veja a página Segurança para as regras de URLs, eventos e estilos.
Implementação: src/rendering/children.ts
Função render() para Montar na Página
Section titled “Função render() para Montar na Página”A função render() monta elementos no DOM:
import { render, html } from '@_bashell/slash'
const App = () => html` <div> <h1>My App</h1> </div>`
// Renderizar em elemento existenteconst root = document.getElementById('app')render(App(), root)
// Ou usando seletor CSSrender(App(), '#app')Assinatura
Section titled “Assinatura”function render( view: Child | (() => Child), container: Element | string | null | undefined): Node | Node[]Parâmetros:
view: Elemento ou função que retorna elementocontainer: Elemento DOM ou seletor CSS
Retorno: Node único ou array de Nodes inseridos
Implementação: src/rendering/render.ts
Comportamentos Especiais
Section titled “Comportamentos Especiais”Cleanup Automático
Section titled “Cleanup Automático”render() limpa children anteriores do container automaticamente:
const root = document.getElementById('app')
// Primeira renderizaçãorender(html`<div>First</div>`, root)
// Segunda renderização - remove 'First' antesrender(html`<div>Second</div>`, root)Hidratação SSR
Section titled “Hidratação SSR”Se o container já tem conteúdo renderizado pelo servidor E existe um <script id="__SLASH_STATE__">, render() lê o JSON do script, remove o script, limpa o container e renderiza a view no cliente. O DOM do servidor não é reaproveitado e o estado do JSON não é aplicado aos seus states (veja Hydration):
// Server-sideconst { html, state } = renderToString(() => App())const output = ` <div id="app">${html}</div> <script id="__SLASH_STATE__" type="application/json">${serializeStateForScript(state)}</script>`
// Client-side: substitui o HTML do servidor por nós renderizados no clienterender(html`<${App} />`, '#app')Erro Handling
Section titled “Erro Handling”render() valida o container e lança erros claros em dev mode:
// Container não encontradorender(App(), '#non-existent')// Error: [slash] render(): selector "#non-existent" not found
// Container nullrender(App(), null)// Error: [slash] render(): container Element is required (received null/undefined)Event Handlers
Section titled “Event Handlers”Event handlers são passados como props prefixadas com on:
Sintaxe Básica
Section titled “Sintaxe Básica”import { html, createState, render } from '@_bashell/slash'
// O state fica fora do componente: se fosse criado dentro, seria recriado a cada renderconst count = createState(0)
const Counter = () => { const increment = () => count.set(count.get() + 1) const decrement = () => count.set(count.get() - 1)
return html` <div> <p>Count: ${count.get()}</p> <button onClick=${increment}>+</button> <button onClick=${decrement}>-</button> </div> `}
// Monte como <${Counter} />: render(Counter(), ...) renderiza uma vez, sem reatividaderender(html`<${Counter} />`, '#app')Eventos Disponíveis
Section titled “Eventos Disponíveis”Todos os eventos DOM padrão são suportados:
const element = html` <input type="text" onInput=${(e) => console.log(e.target.value)} onChange=${handleChange} onFocus=${handleFocus} onBlur=${handleBlur} onKeyDown=${handleKeyDown} onKeyUp=${handleKeyUp} />`Event Object
Section titled “Event Object”Event handlers recebem o evento nativo do browser:
const handleClick = (event: MouseEvent) => { console.log('Clicked at', event.clientX, event.clientY) event.preventDefault() event.stopPropagation()}
const button = html` <button onClick=${handleClick}>Click Me</button>`Event Options
Section titled “Event Options”Para opções avançadas, use array tuple [handler, options]:
const handleScroll = (e: Event) => { console.log('Scrolled')}
const container = html` <div onscroll=${[handleScroll, { passive: true, capture: false }]}> Content </div>`Event Options:
capture: boolean- Captura na fase de capturingpassive: boolean- Listener não chama preventDefault()once: boolean- Listener executado apenas uma vez
Implementação: src/rendering/events.ts
Form Events com Type Safety
Section titled “Form Events com Type Safety”import { html } from '@_bashell/slash'import type { TextFieldEvent } from '@_bashell/slash'
const handleInput = (e: TextFieldEvent<'input'>) => { const value = e.target.value // Type-safe access console.log('Input value:', value)}
const form = html` <form> <input type="text" onInput=${handleInput} /> </form>`Form event types: src/forms/form.types.ts
Cleanup de Event Listeners
Section titled “Cleanup de Event Listeners”Event listeners são automaticamente removidos quando um nó é destruído:
import { destroyNode } from '@_bashell/slash'
const button = html`<button onClick=${handler}>Click</button>`
// Quando não mais necessáriodestroyNode(button as Node) // Remove listener automaticamenteImplementação: src/lifecycle/cleanup.ts
Exemplos Práticos
Section titled “Exemplos Práticos”Exemplo 1: Botão Toggle
Section titled “Exemplo 1: Botão Toggle”import { html, createState, render } from '@_bashell/slash'
const isActive = createState(false)
const ToggleButton = () => { const toggle = () => isActive.set(!isActive.get())
return html` <button class=${isActive.get() ? 'active' : ''} onClick=${toggle} > ${isActive.get() ? 'Active' : 'Inactive'} </button> `}
render(html`<${ToggleButton} />`, '#app')Exemplo 2: Lista Dinâmica
Section titled “Exemplo 2: Lista Dinâmica”import { html, createState, render } from '@_bashell/slash'
const todos = createState<string[]>(['Buy milk', 'Walk dog'])
// Texto em edição fora de qualquer state lido no render: o <input> não é recriado a cada teclalet draft = ''
const TodoList = () => { const addTodo = () => { const value = draft.trim() if (value) { todos.set([...todos.get(), value]) draft = '' } }
return html` <div> <h1>Todos</h1> <ul> ${todos.get().map(todo => html`<li>${todo}</li>`)} </ul> <input type="text" onInput=${(e: Event) => { draft = (e.target as HTMLInputElement).value }} /> <button onClick=${addTodo}>Add</button> </div> `}
render(html`<${TodoList} />`, '#app')Exemplo 3: Form com Validação
Section titled “Exemplo 3: Form com Validação”import { html, createState, render } from '@_bashell/slash'
const form = createState({ email: '', password: '' })const error = createState('')
const handleSubmit = (e: Event) => { e.preventDefault() const { email, password } = form.get()
if (!email.includes('@')) { error.set('Invalid email') return }
if (password.length < 6) { error.set('Password must be at least 6 characters') return }
error.set('') console.log('Login:', { email, password })}
// Só este componente lê `error`: os inputs não são recriados ao validarconst ErrorMessage = () => { const message = error.get() return html`${message && html`<p class="error">${message}</p>`}`}
// `form` só é lido dentro dos handlers, então o form não re-renderiza a cada teclaconst LoginForm = () => html` <form onSubmit=${handleSubmit}> <h1>Login</h1> <${ErrorMessage} /> <input type="email" placeholder="Email" onInput=${(e: Event) => form.set({ ...form.get(), email: (e.target as HTMLInputElement).value }) } /> <input type="password" placeholder="Password" onInput=${(e: Event) => form.set({ ...form.get(), password: (e.target as HTMLInputElement).value }) } /> <button type="submit">Login</button> </form>`
render(html`<${LoginForm} />`, '#app')Próximos Passos
Section titled “Próximos Passos”Agora que você domina renderização básica, explore:
- Sistema de Estado - State management reativo avançado
- Componentes - Criar componentes reutilizáveis
- Batch Updates - Otimizar múltiplas atualizações