omi README

The project's README from github.com/Tencent/omi. As one markdown file: README.md.

English | 简体中文

omi

Omi - Web Components Framework

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

If you want to help the project grow, start by simply sharing it with your peers!

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)

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() {
    this.state.count++
    this.update()
    this.fire('change', this.state.count)
  }

  render() {
    return (
      <>
        <button onClick={this.sub}>-</button>
        <span>{this.state.count}</span>
        <button onClick={this.add}>+</button>
      </>
    )
  }
}

Using in Vue3

<script setup>
import { ref } from 'vue'
// import omi component
import './my-counter'

defineProps({
  msg: String,
})

const count = ref(0)

const change = (e) => {
  count.value = e.detail
}

</script>

<template>
  <h1>{{ msg }}</h1>

  <my-counter @change="change" :count="count" />
  <p>
    【Omi】 
  </p>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
     【Vue】 
    </p>
  </div>

</template>

If you fire the count-change in an Omi component:

this.fire('count-change', this.state.count)

To use the component and listen for events in Vue:

<my-counter @count-change="change" :count="count" />

Using in React

import { useState, useRef, useEffect } from 'react'
import useEventListener from '@use-it/event-listener'
import './my-counter'

function App() {
  const [count, setCount] = useState(100)
  const myCounterRef = useRef(null)

  useEffect(() => {
    const counter = myCounterRef.current
    if (counter) {
      const handleChange = (evt) => {
        setCount(evt.detail)
      }
      counter.addEventListener('change', handleChange)
      return () => {
        counter.removeEventListener('change', handleChange)
      }
    }
  }, [])

  return (
    <>
      <h1>Omi + React</h1>
      <my-counter count={count} ref={myCounterRef}></my-counter>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  )
}

export default App

Contributors

License

MIT © Tencent

omi (Tencent/omi)

Searching Database…

13.3K ·GitHub
这是什么?Omi 是腾讯推出的轻量级 Web Components 框架,在原生自定义元素和 Shadow DOM 之上,增加了 JSX、函数组件、路由、Suspense、指令以及信号驱动的响应式能力。
它是像 React 那样的框架,还是只是对原生自定义元素的简单封装?它是基于原生 Web Components 构建的完整框架:编译后得到真正的自定义元素并使用 Shadow DOM,同时还提供 JSX、路由、Suspense、指令和 Tailwind 支持,组件还可以在 React、Vue 和纯 HTML 中复用。
我该怎么开始一个新项目?使用 Omi CLI 搭建项目,可以选择基础的 Omi + Vite + TS/JS 应用,也可以选择更完整的 Omi + Router + Signal + Suspense + Tailwindcss + Vite + TS 配置。
使用 Omi 一定要用 Vite 或 TypeScript 吗?官方的入门模板基于 Vite 以及 TypeScript 或 JavaScript 构建,并可选集成 Tailwind CSS;但核心的 'omi' 包本身可以用于任何构建环境。
编写 Omi 组件一定要用 JSX 吗?不必须。Omi 同时支持面向对象的类组件和面向数据的函数组件,因此 JSX/面向对象风格和纯响应式数据/函数风格都是可行的做法。
Omi 组件在哪里运行?Omi 组件可以在任何现代浏览器中作为原生自定义元素运行,因此既能独立使用,也能直接放进 React、Vue 或纯 HTML 页面中。
我可以在 React 或 Vue 应用中使用 Omi 组件吗?可以。Omi 编译为标准的自定义元素,README 中展示了在 Vue 3 里直接定义、使用组件并监听其自定义事件的示例,React 同样适用。
Omi 自带哪些现成的 UI 组件库或模板?Omi 提供 TDesign Web Components、基于 Tailwind 的 Omi Elements UI 组件库、100 多个 Omi Templates、Omi Form 表单库,以及 Omi 的 Lucide 图标绑定。
Omi 如何处理组件之间的共享样式?它使用 Constructable Stylesheets(可构造样式表),这是一种浏览器 API,能让多个 Shadow DOM 组件高效地共享和管理同一个样式表对象。
Omi 中的 reactive-signal 是什么?它是驱动 Omi 面向数据编程风格的信号式响应系统,能让界面随信号变化自动更新,TodoApp 示例中展示了这一点。
Creator
Tencent
Size
156 MB
Stars
13.3K
Age
11y
Commits · 90 Days