English | 简体中文
Omi - Web Components Framework
- 📶 Signal-driven reactive programming by reactive-signal
- 🧱 TDesign Web Components
- 💗 100+ OMI Templates & OMI Templates Source Code
- 🐲 OMI Form & OMI Form Playground & Lucide Omi Icons
- ⚡ Tiny size, Fast performance
- 🌐 Everything you need: Web Components, JSX, Function Components, Router, Suspense, Directive, Tailwindcss...
- 💯 Both object oriented programming(OOP) and data oriented programming(DOP) are supported
- 💒 Harness Constructable Stylesheets to easily manage and share styles
import { render, signal, tag, Component, h } from 'omi'
const count = signal(0)
function add() {
count.value++
}
function sub() {
count.value--
}
@tag('counter-demo')
export class CounterDemo extends Component {
static css = 'span { color: red; }'
render() {
return (
<>
<button onClick={sub}>-</button>
<span>{count.value}</span>
<button onClick={add}>+</button>
</>
)
}
}
Use this component:
import { h } from 'omi'
import './counter-demo'
render(<counter-demo />, document.body)
// or
import { CounterDemo, Other } from './counter-demo'
// Prevent tree Shaking when importing other things
render(<CounterDemo />, document.body)
// or
document.body.appendChild(document.createElement('counter-demo'))
Install
npm i omi
To quickly create an Omi + Vite + TS/JS project:
$ npx omi-cli init my-app # or create js project by: npx omi-cli init-js my-app
$ cd my-app
$ npm start # develop
$ npm run build # release
To quickly create an Omi + Router + Signal + Suspense + Tailwindcss + Vite + TS project:
$ npx omi-cli init-spa my-app
$ cd my-app
$ npm start # develop
$ npm run build # release
Packages
- Core packages
omi
- Implementation of omi framework.omi-form
- Powerful, simple and cross frameworks form solution.lucide-omi
- Lucide icon collection for omi.omiu
- Hope to create the best web components. For example, the powerful vchart and vtableomi-router
- Create SPA of omi framework.omi-cli
- To quickly create an Omi + Vite + TS/JS project.
- Starter kits (not published to npm)
omi-elements
- Tailwind Element Omi UI KIT.omi-starter-spa
- A starter repo for building single page app using Omi + OmiRouter + Tailwindcss + TypeScript + Vite + Prettier.omi-starter-ts
- A starter repo for building web app or reusable components using Omi in TypeScript base on Vite.omi-starter-tailwind
- A starter repo for building web app or reusable components using Omi + Tailwindcss + TypeScript + Vite.omi-starter-js
- A starter repo for building web app or reusable components using Omi in JavaScript base on Vite.omi-vue
- Vue SFC + Vite + OMI + OMI-WeUI.
- Components
omi-weui
- WeUI Components of omi.omi-auto-animate
- Omi version of @formkit/auto-animate.omi-suspense
- Handling asynchronous dependencies.
- Directives
omi-transition
- Applying animations when an component is entering and leaving the DOM.omi-ripple
- A lightweight component for adding ripple effects to user interface elements.
- Examples (not published to npm)
snake-game-2tier
- SNake Game withSignal
classsnake-game-3tier
- SNake Game with reactivity functionsomi-tutorial
- Source code of omi tutorial.
If you want to help the project grow, start by simply sharing it with your peers!
- Share via Dev.to
- Share via Twitter
- Share via Facebook
- Share via LinkedIn
- Share via Pinterest
- Share via Reddit
- Share via StumbleUpon
- Share via Vkontakte
- Share via Weibo
- Share via Hackernews
Thank you!
Usage
TodoApp with reactivity functions
Data oriented programming
In data-oriented programming, the focus is on the data itself and the operations on the data, rather than the objects or data structures that hold the data. This programming paradigm emphasizes the change and flow of data, and how to respond to these changes. The TodoApp with reactivity functions is a good example of this, using the concepts of reactive programming, where the UI automatically updates to reflect changes in the data (i.e., the to-do list).
import { render, signal, computed, tag, Component, h } from 'omi'
const todos = signal([
{ text: 'Learn OMI', completed: true },
{ text: 'Learn Web Components', completed: false },
{ text: 'Learn JSX', completed: false },
{ text: 'Learn Signal', completed: false }
])
const completedCount = computed(() => {
return todos.value.filter(todo => todo.completed).length
})
const newItem = signal('')
function addTodo() {
// api a
todos.value.push({ text: newItem.value, completed: false })
todos.update() // Trigger UI auto update
// api b, same as api a
// todos.value = [...todos.value, { text: newItem.value, completed: false }]
newItem.value = '' // Changing the value type can automatically update the UI
}
function removeTodo(index: number) {
todos.value.splice(index, 1)
todos.update() // Trigger UI auto update
}
@tag('todo-list')
class TodoList extends Component {
onInput = (event: Event) => {
const target = event.target as HTMLInputElement
newItem.value = target.value
}
render() {
return (
<>
<input type="text" value={newItem.value} onInput={this.onInput} />
<button onClick={addTodo}>Add</button>
<ul>
{todos.value.map((todo, index) => {
return (
<li>
<label>
<input
type="checkbox"
checked={todo.completed}
onInput={() => {
todo.completed = !todo.completed
todos.update() // Trigger UI auto update
}}
/>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</label>
{' '}
<button onClick={() => removeTodo(index)}>❌</button>
</li>
)
})}
</ul>
<p>Completed count: {completedCount.value}</p>
</>
)
}
}
render(<todo-list />, document.body)
TodoApp with Signal Class
Object oriented programming
In object-oriented programming, the focus is on the objects, which contain both data and methods to operate on the data. This programming paradigm emphasizes the interaction and cooperation between objects, and how to organize and manage code through object encapsulation, inheritance, and polymorphism. The TodoApp with reactivity functions can also be implemented in an object-oriented way, for example, by creating a TodoList class that contains the data of the to-do list and methods to operate on this data, as well as a update
method to update the UI.
import { render, Signal, tag, Component, h, computed } from 'omi'
type Todo = { text: string, completed: boolean }
class TodoApp extends Signal<{ todos: Todo[], filter: string, newItem: string }> {
completedCount: ReturnType<typeof computed>
constructor(todos: Todo[] = []) {
super({ todos, filter: 'all', newItem: '' })
this.completedCount = computed(() => this.value.todos.filter(todo => todo.completed).length)
}
addTodo = () => {
// api a
this.value.todos.push({ text: this.value.newItem, completed: false })
this.value.newItem = ''
this.update()
// api b, same as api a
// this.update((value) => {
// value.todos.push({ text: value.newItem, completed: false })
// value.newItem = ''
// })
}
toggleTodo = (index: number) => {
const todo = this.value.todos[index]
todo.completed = !todo.completed
this.update()
}
removeTodo = (index: number) => {
this.value.todos.splice(index, 1)
this.update()
}
}
const todoApp = new TodoApp([
{ text: 'Learn OMI', completed: true },
{ text: 'Learn Web Components', completed: false },
{ text: 'Learn JSX', completed: false },
{ text: 'Learn Signal', completed: false }
])
@tag('todo-list')
class TodoList extends Component {
onInput = (event: Event) => {
const target = event.target as HTMLInputElement
todoApp.value.newItem = target.value
}
render() {
const { todos } = todoApp.value
const { completedCount, toggleTodo, addTodo, removeTodo } = todoApp
return (
<>
<input type="text" value={todoApp.value.newItem} onInput={this.onInput} />
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, index) => {
return (
<li>
<label>
<input
type="checkbox"
checked={todo.completed}
onInput={() => toggleTodo(index)}
/>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</label>
{' '}
<button onClick={() => removeTodo(index)}>❌</button>
</li>
)
})}
</ul>
<p>Completed count: {completedCount.value}</p>
</>
)
}
}
render(<todo-list />, document.body)
We won't discuss which method is good or bad here. You can choose either method using omi.
Auto Import h
vite.config.js:
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxInject: "import { h } from 'omi'",
jsxFactory: "h",
jsxFragment: "h.f"
}
})
You can inject code during construction, so you don't have to manually export h
.
Define Cross Framework Component
The case of using Omi component in Vue is as follows:
my-counter.tsx:
import { tag, Component, h, bind } from 'omi'
@tag('my-counter')
class MyCounter extends Component {
static props = {
count: {
type: Number,
default: 0,
changed(newValue, oldValue) {
this.state.count = newValue
this.update()
}
}
}
state = {
count: null
}
install() {
this.state.count = this.props.count
}
@bind
sub() {
this.state.count--
this.update()
this.fire('change', this.state.count)
}
@bind
add() {