Skip to content

Migração e Integração

Este guia cobre como adotar Slash em diferentes contextos: migrando de outras bibliotecas, integrando com projetos existentes e configurando ferramentas de build.


Terminal window
bun add @ezbug/slash

ReactSlashExemplo
useState()createState()const [count, setCount] = useState(0)const count = createState(0)
useEffect()state.watch() ou código diretoVer exemplos abaixo
useMemo()Funções normaisNão necessário (reactive por natureza)
useCallback()Funções normaisNão necessário
useRef()Variáveis locaislet ref = null
useContext()Estado globalcreateState() fora do componente
propsParâmetros de funçãoIdêntico
JSXhtml template tagVer exemplos
// React
import { useState } from 'react'
function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
)
}
// Slash
import { html, createState } from '@ezbug/slash'
interface CounterProps {
initialCount?: number
}
function Counter({ initialCount = 0 }: CounterProps) {
const count = createState(initialCount)
return html`
<div>
<p>Count: ${count.get()}</p>
<button onclick=${() => count.set(count.get() + 1)}>
Increment
</button>
<button onclick=${() => count.set(count.get() - 1)}>
Decrement
</button>
</div>
`
}

Você pode migrar componente por componente sem reescrever tudo:

  1. Identifique componentes folha (sem filhos React)
  2. Migre de baixo para cima
  3. Use micro-frontends se necessário
// React component que usa Slash component
import { useEffect, useRef } from 'react'
import { render } from '@ezbug/slash'
import { SlashComponent } from './slash-components'
function ReactWrapper() {
const containerRef = useRef(null)
useEffect(() => {
if (containerRef.current) {
render(SlashComponent(), containerRef.current)
}
}, [])
return <div ref={containerRef} />
}

Vue 3SlashNotas
ref()createState().value vs .get()/.set()
computed()FunçõesCompute on-demand
watch()state.watch()API similar
v-ifTernário ? :JS nativo
v-for.map()JS nativo
v-modelvalue + oninputManual binding
onMounted()Código diretoExecuta ao criar
onUnmounted()onCleanup()Cleanup
Templatehtml tagHTM syntax
<!-- Vue 3 -->
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Doubled: {{ doubled }}</p>
<button @click="increment">+</button>
</div>
</template>

<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { html, render, createState } from 'https://esm.sh/@ezbug/slash'
const App = () => {
const count = createState(0)
return html`
<div>
<h1>Hello Slash!</h1>
<p>Count: ${count.get()}</p>
<button onclick=${() => count.set(count.get() + 1)}>
Increment
</button>
</div>
`
}
render(App(), document.getElementById('app'))
</script>
</body>
</html>

Slash pode conviver com jQuery ou vanilla JS:

import { html, render, createState } from '@ezbug/slash'
// Estado Slash
const globalState = createState({
user: null,
isLoggedIn: false
})
// Component Slash
function UserWidget() {
const state = globalState.get()
if (!state.isLoggedIn) {
return html`<button onclick=${showLoginModal}>Login</button>`
}
return html`<p>Welcome, ${state.user.name}!</p>`
}
// Integração com jQuery
function showLoginModal() {
$('#loginModal').modal('show')
}
$('#loginForm').on('submit', function(e) {
e.preventDefault()
// Atualiza estado Slash
globalState.set({
user: { name: 'John' },
isLoggedIn: true
})
$('#loginModal').modal('hide')
})
// Render componente Slash
render(UserWidget(), document.getElementById('user-widget'))

Encapsule Slash em Web Components:

import { html, render, createState } from '@ezbug/slash'
class CounterElement extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' })
const count = createState(0)
const Counter = html`
<div>
<p>Count: ${count.get()}</p>
<button onclick=${() => count.set(count.get() + 1)}>
Increment
</button>
</div>
`
render(Counter, shadow)
}
}
customElements.define('slash-counter', CounterElement)

Uso:

<slash-counter></slash-counter>

Terminal window
bun create vite my-app --template vanilla-ts
cd my-app
bun add @ezbug/slash
src/main.ts
import { html, render, createState } from '@ezbug/slash'
import './style.css'
const App = () => {
const count = createState(0)
return html`
<div>
<h1>Vite + Slash</h1>
<p>Count: ${count.get()}</p>
<button onclick=${() => count.set(count.get() + 1)}>
Increment
</button>
</div>
`
}
const root = document.querySelector('#app')
if (root) {
render(App(), root)
}
vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
// Configuração padrão funciona!
})
Terminal window
bun init
bun add @ezbug/slash
index.ts
import { html, render, createState } from '@ezbug/slash'
const App = () => {
const count = createState(0)
return html`
<div>
<h1>Bun + Slash</h1>
<button onclick=${() => count.set(count.get() + 1)}>
Count: ${count.get()}
</button>
</div>
`
}
render(App(), document.body)
package.json
{
"scripts": {
"dev": "bun run --watch index.ts",
"build": "bun build index.ts --outdir ./dist --minify"
}
}
Terminal window
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install @ezbug/slash
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts', '.js']
},
devServer: {
static: './dist'
}
}
Terminal window
npm install --save-dev parcel
npm install @ezbug/slash
index.html
<!DOCTYPE html>
<html>
<head>
<title>Parcel + Slash</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/index.ts"></script>
</body>
</html>
package.json
{
"scripts": {
"dev": "parcel index.html",
"build": "parcel build index.html"
}
}
Terminal window
npm install --save-dev esbuild
npm install @ezbug/slash
build.js
require('esbuild').build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js',
minify: true,
sourcemap: true
}).catch(() => process.exit(1))
package.json
{
"scripts": {
"build": "node build.js"
}
}

tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"types": ["vite/client"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

Terminal window
bun add express @ezbug/slash
bun add -d @types/express
server.ts
import express from 'express'
import { renderToString, htmlString } from '@ezbug/slash'
import { App } from './App'
const app = express()
app.get('*', (req, res) => {
const appHtml = renderToString(App())
const html = htmlString`
<!DOCTYPE html>
<html>
<head>
<title>SSR App</title>
<script type="module" src="/client.js"></script>
</head>
<body>
<div id="app">${appHtml}</div>
</body>
</html>
`
res.send(html)
})
app.listen(3000, () => {
console.log('Server running on http://localhost:3000')
})
server.ts
import { renderToString, htmlString } from '@ezbug/slash'
import { App } from './App'
Bun.serve({
port: 3000,
fetch(req) {
const appHtml = renderToString(App())
const html = htmlString`
<!DOCTYPE html>
<html>
<head>
<title>SSR App</title>
</head>
<body>
<div id="app">${appHtml}</div>
<script type="module" src="/client.js"></script>
</body>
</html>
`
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
}
})

my-app/
├── src/
│ ├── components/
│ │ ├── Header.ts
│ │ ├── Footer.ts
│ │ └── TodoItem.ts
│ ├── pages/
│ │ ├── Home.ts
│ │ ├── About.ts
│ │ └── NotFound.ts
│ ├── state/
│ │ └── todoState.ts
│ ├── router.ts
│ ├── App.ts
│ └── main.ts
├── public/
│ └── styles.css
├── index.html
├── package.json
└── tsconfig.json
my-app/
├── src/
│ ├── shared/
│ │ ├── components/
│ │ ├── loaders/
│ │ └── types.ts
│ ├── server/
│ │ ├── index.ts
│ │ └── routes.ts
│ ├── client/
│ │ └── index.ts
│ └── App.ts
├── package.json
└── tsconfig.json

Counter.test.ts
import { test, expect } from 'bun:test'
import { createState } from '@ezbug/slash'
test('counter increments', () => {
const count = createState(0)
expect(count.get()).toBe(0)
count.set(5)
expect(count.get()).toBe(5)
})
test('state watches changes', () => {
const count = createState(0)
let called = false
count.watch((newVal) => {
called = true
expect(newVal).toBe(10)
})
count.set(10)
expect(called).toBe(true)
})
Terminal window
bun add -d vitest happy-dom
vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'happy-dom'
}
})

Não reescreva tudo de uma vez. Comece com:

  • Componentes novos
  • Componentes folha (sem filhos)
  • Features isoladas

Slash tem tipos excelentes. Use para facilitar migração:

// Defina interfaces para seus dados
interface User {
id: string
name: string
email: string
}
const user = createState<User | null>(null)
src/state/global.ts
import { createState } from '@ezbug/slash'
export const authState = createState({
user: null,
isAuthenticated: false
})
export const uiState = createState({
sidebarOpen: false,
theme: 'light'
})
src/utils/form.ts
import { createState } from '@ezbug/slash'
export function createFormField<T>(initialValue: T) {
const value = createState(initialValue)
const error = createState<string | null>(null)
const touched = createState(false)
return {
value,
error,
touched,
reset: () => {
value.set(initialValue)
error.set(null)
touched.set(false)
}
}
}