API Reference
API Reference
Section titled “API Reference”Documentação completa de todas as funções, componentes e tipos exportados pelo Slash.
Instalação
Section titled “Instalação”bun add @_bashell/slash# ounpm install @_bashell/slashRenderização
Section titled “Renderização”Cria elementos DOM usando hyperscript.
function h( tag: string | Function, props?: Props | null, ...children: Child[]): NodeParâmetros:
tag: Nome da tag HTML ou componente funcionalprops: Propriedades do elemento (opcional)children: Filhos do elemento (strings, números,Nodes, arrays, reativos)
Retorna: Node - o elemento DOM real (o Slash não tem VNodes)
Exemplo:
import { h } from '@_bashell/slash'
const element = h('div', { class: 'container' }, h('h1', null, 'Hello'), h('p', null, 'World'))Template tag (HTM) que converte template strings em nós DOM. Seu tipo de retorno é Child; na prática, um Node (ou array de nós).
const html: HTMTemplateUso:
import { html } from '@_bashell/slash'
const element = html` <div class="container"> <h1>Hello</h1> <p>World</p> </div>`Interpolação:
const name = 'John'const items = ['a', 'b', 'c']
const element = html` <div> <h1>Hello ${name}</h1> <ul> ${items.map(item => html`<li>${item}</li>`)} </ul> </div>`render()
Section titled “render()”Monta uma view no DOM. Se o container já tiver HTML do servidor e a página tiver o script __SLASH_STATE__, hidrata (veja Hydration).
function render( view: Child | (() => Child), container: Element | string | null | undefined): Node | Node[]Parâmetros:
view: o que renderizar (umChild, ou uma função que o retorna)container: elemento DOM ou seletor CSS ('#app'). Lança erro se o seletor não encontrar elemento
Retorna: o Node (ou Node[]) montado.
Exemplo:
import { html, render } from '@_bashell/slash'
const App = html`<h1>My App</h1>`const root = document.getElementById('app')
if (root) { render(App, root)}destroyNode()
Section titled “destroyNode()”Remove um nó do DOM e limpa recursos.
function destroyNode(element: HTMLElement): voidParâmetros:
element: Elemento DOM para destruir
Uso:
import { destroyNode } from '@_bashell/slash'
const element = document.getElementById('my-component')if (element) { destroyNode(element)}Estado Reativo
Section titled “Estado Reativo”createState()
Section titled “createState()”Cria um container de estado observável: watchers são notificados quando o valor muda e componentes que leem o estado com get() re-renderizam automaticamente (monte-os como <${Comp} />). Passar o próprio State como child ou prop (${count}) não é reativo: use ${count.get()} dentro de um componente.
function createState<T>( initialValue: T, options?: StateOptions): State<T>Parâmetros:
initialValue: Valor inicial do estadooptions: Opções de configuração (opcional)
Opções:
interface StateOptions { enableHistory?: boolean // Habilita histórico (time-travel debug) historyMaxSize?: number // Tamanho máximo do histórico (padrão: 100)}Retorna: Objeto State<T> com métodos:
state.get()
Section titled “state.get()”Retorna o valor atual do estado.
get(): TExemplo:
const count = createState(0)console.log(count.get()) // 0state.set()
Section titled “state.set()”Atualiza o valor do estado e notifica observadores.
set(newValue: T): voidExemplo:
const count = createState(0)count.set(5)console.log(count.get()) // 5state.watch()
Section titled “state.watch()”Registra um observador que é chamado quando o estado muda.
watch(callback: (newValue: T) => void): () => voidParâmetros:
callback: Função chamada ao mudar estado
Retorna: Função de cleanup para remover o observador
Exemplo:
const count = createState(0)
const unwatch = count.watch((newVal) => { console.log(`Changed to ${newVal}`)})
count.set(5) // Logs: "Changed to 5"
// Remover observadorunwatch()Ordem e reentrância: watchers são notificados na ordem de registro. Se um watcher chamar set() no mesmo estado durante a notificação, vale o último valor: a notificação aninhada entrega o valor atual a todos os watchers e a notificação antiga é interrompida, então nenhum watcher recebe um valor velho depois do novo. Watchers anteriores ao que fez o set veem o valor antigo e depois o novo. set segue síncrono, e um erro lançado na notificação aninhada chega a quem chamou set.
Exemplo Completo:
import { createState, html, render } from '@_bashell/slash'
const count = createState(0, { enableHistory: true })
// Componente que usa estadoconst Counter = () => html` <div> <p>Count: ${count.get()}</p> <button onClick=${() => count.set(count.get() + 1)}> Increment </button> </div>`
// Monte como <${Counter} />: chamado diretamente (Counter()), o componente não é reativorender(html`<${Counter} />`, '#app')batch()
Section titled “batch()”Agrupa múltiplas atualizações de estado: cada estado alterado notifica seus watchers uma única vez, com o valor final, no fim do batch mais externo.
function batch(fn: () => void): voidParâmetros:
fn: Função síncrona que contém as atualizações
Exemplo:
import { createState, batch } from '@_bashell/slash'
const name = createState('')const age = createState(0)
name.watch((v) => console.log('name:', v))age.watch((v) => console.log('age:', v))
// Sem batch: cada set notifica na horaname.set('John')age.set(30)
// Com batch: uma notificação por estado alterado, no fimbatch(() => { name.set('Jane') name.set('Joan') // só 'Joan' é notificado age.set(31)})Semântica:
- Só notificam os estados cujo valor mudou; sem mudança, ninguém é notificado.
- Batches aninhados notificam apenas no fim do batch mais externo.
- Erros de watchers são isolados: todos os watchers rodam e o primeiro erro é relançado no final.
- Se
fnlançar, as notificações do que já mudou ainda acontecem e o erro chega a quem chamoubatch. batchnão funciona com funções assíncronas.
Detalhes em Batch Updates.
Server-Side Rendering
Section titled “Server-Side Rendering”renderToString()
Section titled “renderToString()”Renderiza uma view para HTML (síncrono) e devolve também o estado a serializar.
function renderToString(view: Child | (() => Child)): { html: string state: Record<string, unknown>}Parâmetros:
view: filho ou função que retorna o filho (usehtmlStringnos templates)
Retorna: { html, state }. Reativos com get() + subscribe() (como o Router) são emitidos entre marcadores <!--reactive-start:id--> sem gravar o valor em state. Um State não é reativo no SSR: interpole state.get() para renderizar o valor.
Exemplo:
import { htmlString, renderToString, serializeStateForScript } from '@_bashell/slash/ssr'
const { html, state } = renderToString(() => htmlString`<h1>Hello SSR</h1>`)
const page = `<div id="app">${html}</div><script id="__SLASH_STATE__" type="application/json">${serializeStateForScript(state)}</script>`html é sempre uma string. Monte o documento (<!DOCTYPE>, <head>, scripts) com um template literal comum; o que você colocar nele à mão (um título, por exemplo) precisa ser escapado por você.
serializeStateForScript()
Section titled “serializeStateForScript()”Serializa o state para uso dentro de <script type="application/json">.
function serializeStateForScript(state: unknown): stringFaz JSON.stringify e escapa <, >, &, U+2028 e U+2029, para que os valores não possam fechar a tag <script>. Use sempre no lugar de JSON.stringify cru.
renderToStream()
Section titled “renderToStream()”Renderiza uma view em chunks (async generator de strings). O último chunk é a tag <script id="__SLASH_STATE__"> com o estado já serializado.
function renderToStream( view: Child | (() => Child)): AsyncGenerator<string, void, unknown>Exemplo:
import { htmlString, renderToStream } from '@_bashell/slash/ssr'
const App = () => htmlString`<h1>Hello Streaming</h1>`
response.writeHead(200, { 'Content-Type': 'text/html' })for await (const chunk of renderToStream(App)) { response.write(chunk)}response.end()htmlString
Section titled “htmlString”Template tag que renderiza HTML no servidor e devolve um SafeHtml. Toda string interpolada é escapada. Templates htmlString aninhados, componentes e listas de templates (items.map(...)) entram no pai sem escape duplo porque são SafeHtml.
const htmlString: (strings: TemplateStringsArray, ...values: unknown[]) => SafeHtmlUso:
import { htmlString } from '@_bashell/slash/ssr'
const title = 'My Page <script>'const page = htmlString`<h1>${title}</h1>`String(page)// <h1>My Page <script></h1>Use String(page) (ou renderToString(() => page).html) quando precisar de uma string. Valores dinâmicos dentro de <script>/<style> são descartados (com aviso em dev); para JSON use unsafeHtml(serializeStateForScript(dados)). Um < literal dentro de um <script> estático do template é lido como tag (limitação do htm): coloque esse código em unsafeHtml(...).
unsafeHtml()
Section titled “unsafeHtml()”Marca uma string como marcação confiável, para ser emitida como HTML sem escape. Funciona em html (cliente) e em htmlString (servidor).
function unsafeHtml(html: string): SafeHtmlimport { 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>`isSafeHtml() e SafeHtml
Section titled “isSafeHtml() e SafeHtml”function isSafeHtml(x: unknown): x is SafeHtmlSafeHtml é o tipo devolvido por htmlString e unsafeHtml; toString() devolve a marcação. isSafeHtml não pode ser enganado por JSON vindo da rede (a marca é uma propriedade própria com Symbol.for). Um SafeHtml guardado em estado reativo perde a marca ao ser serializado para hidratação e volta como texto; reembrulhe com unsafeHtml no cliente.
unsafeUrl()
Section titled “unsafeUrl()”Marca uma URL confiável cujo esquema a política de URLs bloqueia (por exemplo myapp://abrir/42).
function unsafeUrl(url: string): SafeUrlimport { html, unsafeUrl } from '@_bashell/slash/core'
html`<a href=${unsafeUrl('myapp://abrir/42')}>Abrir no app</a>`isSafeUrl() e SafeUrl
Section titled “isSafeUrl() e SafeUrl”function isSafeUrl(x: unknown): x is SafeUrlSafeUrl é o tipo devolvido por unsafeUrl. Assim como no SafeHtml, a marca não é forjável por JSON e se perde ao serializar estado para hidratação (a URL volta a ser sanitizada).
Veja a política completa de URLs em Segurança.
sanitizeUrl() e BLOCKED_URL
Section titled “sanitizeUrl() e BLOCKED_URL”function sanitizeUrl(attr: string, value: string, tag?: string): stringconst BLOCKED_URL: 'about:blank#blocked'Aplica a política de URLs do Slash a um valor e devolve o próprio valor ou BLOCKED_URL. Exportados por @_bashell/slash/core e @_bashell/slash/ssr. attr é o nome do atributo ('href', 'src'…) e tag (opcional, por exemplo 'img') importa porque blob: e data:image/svg+xml só passam em contextos de imagem e mídia. Em dev, um valor bloqueado emite o mesmo aviso dos templates.
import { sanitizeUrl, BLOCKED_URL } from '@_bashell/slash/core'
const base = new URL('https://app.example.com')
function destinoSeguro(next: string): string { if (sanitizeUrl('href', next) === BLOCKED_URL) return '/' try { const url = new URL(next, base) return url.origin === base.origin ? url.pathname + url.search + url.hash : '/' } catch { return '/' }}sanitizeUrl valida o esquema, não o destino: //evil.com e https://evil.com passam. Para um redirect, confira também a origem, como acima.
Universal Data Loading
Section titled “Universal Data Loading”createLoader()
Section titled “createLoader()”Cria um loader isomórfico com cache no cliente. No servidor o loader sempre executa (sem cache).
function createLoader<T>( fn: (ctx: LoaderContext) => T | Promise<T>, options: LoaderOptions): (ctx: LoaderContext) => Promise<T>Contexto e opções:
type LoaderContext = { params: Record<string, string> request?: Request isServer: boolean}
type LoaderOptions = { key: string // identifica o loader no cache (obrigatório) ttl?: number // tempo de vida do cache em ms (padrão: 5 minutos) revalidate?: boolean // ignora o cache e executa de novo}A chave de cache no cliente é `${key}:${JSON.stringify(ctx.params)}`.
Exemplo:
import { createLoader } from '@_bashell/slash'
const userLoader = createLoader( async ({ params }) => { const res = await fetch(`/api/users/${params.id}`) return res.json() }, { key: 'user', ttl: 5 * 60 * 1000 })
// Uso (assíncrono)const user = await userLoader({ params: { id: '123' }, isServer: false }) // carrega e cacheiaconst same = await userLoader({ params: { id: '123' }, isServer: false }) // vem do cacheinvalidateLoader()
Section titled “invalidateLoader()”Limpa o cache de loaders no cliente.
function invalidateLoader(key?: string): voidParâmetros:
key:keydo loader cujas entradas serão removidas (opcional). Sem argumento, limpa todo o cache.
import { invalidateLoader } from '@_bashell/slash'
invalidateLoader('user') // todas as entradas do loader com key 'user'invalidateLoader() // todo o cacheserializeLoaderData() e deserializeLoaderData()
Section titled “serializeLoaderData() e deserializeLoaderData()”function serializeLoaderData(data: Record<string, unknown>): string // JSON.stringify + escape para <script>function deserializeLoaderData(serialized: string): Record<string, unknown> // {} se inválidohydrateLoaderCache()
Section titled “hydrateLoaderCache()”Injeta no cache do cliente dados que vieram do servidor.
function hydrateLoaderCache(data: Record<string, unknown>): voidExemplo (Cliente):
import { hydrateLoaderCache, deserializeLoaderData } from '@_bashell/slash'
const serialized = document.getElementById('__LOADER_DATA__')?.textContent || '{}'hydrateLoaderCache(deserializeLoaderData(serialized))Exemplo (Servidor): veja Data Loading.
isServer()
Section titled “isServer()”Detecta se o código está rodando no servidor.
function isServer(): booleanRetorna: true se no servidor, false no cliente
Exemplo:
import { isServer } from '@_bashell/slash'
if (isServer()) { // Código apenas para servidor console.log('Running on server')} else { // Código apenas para cliente console.log('Running on client')}Roteamento
Section titled “Roteamento”createRouter()
Section titled “createRouter()”Cria um roteador. O retorno é um State<RouterState> com métodos extras de navegação.
function createRouter(config: RouterConfig): RouterInstanceConfiguração:
type RouterConfig = { routes: RouteConfig[] mode?: 'history' | 'hash' // padrão: 'history' fallback?: string // caminho usado quando nenhuma rota casa guards?: NavigationGuard[] // guards globais initialPath?: string // caminho inicial (SSR e hidratação)}
type RouteConfig = { path: string // ex.: '/users/:id' component: (state: RouterState) => any name?: string meta?: Record<string, any> guards?: NavigationGuard[] // guards desta rota children?: RouteConfig[] // rotas aninhadas}Um guard recebe (to, from) e retorna void/true (permite), false (bloqueia) ou uma string (redireciona). Não há lazy loading, data loaders nem curingas (*): use fallback para o 404.
Exemplo:
import { html, render } from '@_bashell/slash'import { createRouter, Router, Link } from '@_bashell/slash/router'
const router = createRouter({ mode: 'history', fallback: '/404', routes: [ { path: '/', component: () => html`<h1>Home</h1>` }, { path: '/users/:id', component: (state) => html`<h1>User ${state.params.id}</h1>`, meta: { requiresAuth: true } }, { path: '/404', component: () => html`<h1>Not found</h1>` }, ], guards: [(to) => { console.log(`Navigating to ${to.path}`) }],})
const App = () => html` <div> <nav><${Link} to="/" router=${router}>Home<//></nav> <main><${Router} router=${router} /></main> </div>`
render(html`<${App} />`, '#app')Router Component
Section titled “Router Component”Renderiza o component da rota atual e se atualiza a cada navegação. É um reativo (get() + subscribe()), então funciona como componente em qualquer posição do template.
html`<${Router} router=${router} />`// a forma direta também é válida: ${Router({ router })}No servidor, Router funciona dentro de htmlString/renderToString: o HTML da rota é emitido entre marcadores <!--reactive-start:id--> e não é gravado em state. Passe initialPath ao createRouter para resolver a rota no servidor. Veja SSR.
Link Component
Section titled “Link Component”Renderiza um <a href> cujo clique é interceptado e chama router.push(). Props extras (como class) são repassadas ao <a>.
html`<${Link} to="/about" router=${router} class="nav-link">About<//>`Props: to: string, router: RouterInstance (obrigatório), external?: boolean e children.
to precisa ser um caminho do app (/x, ?q ou #h). Qualquer outra coisa (./x, ../x, about, //host, javascript:, https://...) não navega: o clique recebe preventDefault e o href vira about:blank#blocked. Para um site externo, use external: só libera http(s), mailto:, sms: e tel: e adiciona rel="noopener noreferrer".
html`<${Link} to="https://example.com/docs" external router=${router}>Docs<//>`// <a href="https://example.com/docs" rel="noopener noreferrer">Docs</a>?q e #h são relativos à página atual. O roteador só intercepta cliques em links http(s) da mesma origem (respeitando <base>); com ctrl, meta, shift ou alt, botão que não seja o esquerdo, target diferente de _self ou download, o navegador age normalmente. Se o navegador recusar um router.push()/replace(), a promise rejeita com Navigation failed: ... e o estado do roteador continua igual à URL.
Router Instance
Section titled “Router Instance”router.get() // { currentRoute, params, query, meta, isNavigating }router.watch((state) => { /* ... */ })
await router.push('/users/7') // navega e adiciona ao históricoawait router.replace('/about') // navega sem adicionar ao históricorouter.back()router.forward()router.go(-2) // volta 2 páginasrouter.currentRoute() // RouteMatch | nullError Handling
Section titled “Error Handling”ErrorBoundary
Section titled “ErrorBoundary”Componente com fallback para erros. Limitação importante: o html avalia os filhos de forma eager, antes de chamar o componente. Um erro lançado ao construir os filhos (por exemplo <${Quebrado} /> dentro do boundary) acontece antes do ErrorBoundary rodar e não é capturado por ele. Para proteger uma subárvore, use safeRender, que recebe uma função e chama o fallback se ela lançar.
function ErrorBoundary(props: ErrorBoundaryProps): Child
type ErrorBoundaryProps = { fallback: (error: Error) => Child // obrigatório onError?: (error: Error, errorInfo: { componentStack?: string }) => void children: Child}Exemplo (proteja a subárvore com safeRender):
import { html, safeRender } from '@_bashell/slash'
function Quebrado(): never { throw new Error('boom')}
const view = safeRender( () => html`<main><${Quebrado} /></main>`, (error) => html` <div class="error"> <h2>Something went wrong</h2> <p>${error.message}</p> </div> `)catchAsync()
Section titled “catchAsync()”Envolve uma função assíncrona e devolve uma tupla [safeFn, getError].
function catchAsync<T>( fn: () => Promise<T>, onError?: (error: Error) => void): [() => Promise<T | null>, () => Error | null]safeFn()executafn; se lançar, guarda o erro, chamaonErrore resolve comnull.getError()devolve o último erro (ounull; é limpo no início de cadasafeFn()).
Exemplo:
import { catchAsync } from '@_bashell/slash'
const [loadData, getError] = catchAsync( async () => { const res = await fetch('/api/data') return res.json() }, (error) => { console.error('Fetch failed:', error) })
const data = await loadData() // dados, ou null se falhouif (data === null) { console.log('Erro:', getError()?.message)}safeRender()
Section titled “safeRender()”Executa uma função de view e, se ela lançar, registra o erro no console e devolve o fallback.
function safeRender( view: () => Child, fallback: (error: Error) => Child // obrigatório): ChildExemplo:
import { html, safeRender } from '@_bashell/slash'
const App = () => html`<h1>Hello</h1>`
const view = safeRender( () => App(), (error) => html`<h1>Error: ${error.message}</h1>`)No SSR, use htmlString (de @_bashell/slash/ssr) em vez de html na view e no fallback.
setupGlobalErrorHandler()
Section titled “setupGlobalErrorHandler()”Configura um handler global de erros.
function setupGlobalErrorHandler( onError: (error: Error, source: 'render' | 'runtime') => void): voidExemplo:
import { setupGlobalErrorHandler } from '@_bashell/slash'
setupGlobalErrorHandler((error) => { console.error('Global error:', error) // Enviar para serviço de logging})Formulários
Section titled “Formulários”Helpers de formulário
Section titled “Helpers de formulário”Importados de @_bashell/slash (ou de @_bashell/slash/forms). Não existe um tipo FormHelpers<T>: o Slash oferece funções pequenas que ligam inputs a um State.
| Função | Para que serve |
|---|---|
textFieldControl(state) | Props value/onInput para <input> e <textarea>, ligados a State<{ value: string }> |
checkboxControl(state) | Props checked/onChange para checkbox, com State<{ value: boolean }> |
radioControl(state, value) | Props para um radio do grupo, com State<{ value: string }> |
SelectControl(state) | Props para <select>, com State<{ value: string }> |
getText(e), getChecked(e), getSelectValue(e) | Leem o valor de eventos tipados |
onSubmit(cb) | preventDefault() e entrega os dados do <form> como objeto |
onReset(handler), onButtonClick(handler) | Handlers tipados |
formToObject(form) | Converte um <form> em objeto sem protótipo (Object.create(null); campos repetidos viram arrays). Use Object.hasOwn(obj, 'campo') em vez de obj.hasOwnProperty(...) |
delegate(root, type, selector, handler) | Delegação de eventos; retorna função para remover o listener |
Exemplo:
import { html, render, onSubmit } from '@_bashell/slash'
const log = (data: Record<string, unknown>) => console.log(data)
const Form = () => html` <form onSubmit=${onSubmit(log)}> <input name="email" type="email" /> <button type="submit">Enviar</button> </form>`
render(Form(), '#app')Veja os conceitos, o comportamento de foco e os exemplos completos em Formulários e Form Validation.
Developer Experience
Section titled “Developer Experience”setDevMode()
Section titled “setDevMode()”Habilita/desabilita modo de desenvolvimento.
function setDevMode(enabled: boolean): voidExemplo:
import { setDevMode } from '@_bashell/slash'
setDevMode(process.env.NODE_ENV === 'development')setWarningsEnabled()
Section titled “setWarningsEnabled()”Habilita/desabilita warnings no console.
function setWarningsEnabled(enabled: boolean): voidisDevMode()
Section titled “isDevMode()”Verifica se está em modo dev.
function isDevMode(): booleanisWarningsEnabled()
Section titled “isWarningsEnabled()”Verifica se warnings estão habilitados.
function isWarningsEnabled(): booleanTipos TypeScript
Section titled “Tipos TypeScript”Propriedades de um elemento (Record<string, unknown> | null). Handlers de evento usam camelCase (onClick, onInput).
type Props = Record<string, unknown> | nulltype EventHandler = EventListenerOrEventListenerObjectState<T>
Section titled “State<T>”Tipo retornado por createState().
type State<T> = { get: () => T set: (value: T) => void // O callback recebe só o novo valor; retorna a função de cancelamento watch: (callback: (value: T) => void) => () => void // Presentes quando o state foi criado com { enableHistory: true } getHistory?: () => Readonly<StateHistory<T>> clearHistory?: () => void}Tipos válidos para children de elementos.
type Child = | Node | string | SafeHtml | number | boolean | null | undefined | Reactive<unknown> // { get(), subscribe(fn) } | Child[] | readonly Child[]Próximos Passos
Section titled “Próximos Passos”- Exemplos Práticos - Aplicações completas
- Guias Avançados - Técnicas avançadas
- Slash vs React - Comparação com outras libs