omi/README.md

453 lines
13 KiB
Markdown
Raw Normal View History

2018-10-19 09:29:34 +08:00
English | [简体中文](./README.CN.md)
2018-10-14 07:50:55 +08:00
2018-10-20 10:52:43 +08:00
<p align="center"><img src="./assets/omi-logo.svg" alt="omi" width="300"/></p>
<h2 align="center">Omi: Next Generation Web Framework in 4kb JavaScript</h2>
<p align="center"><b>Merge JSX, Web Components, Proxy, Path Updating together</b></p>
2018-05-03 10:46:34 +08:00
2018-10-20 10:52:43 +08:00
## Why Omi?
2018-05-03 10:46:34 +08:00
2018-10-20 10:52:43 +08:00
- Tiny size. *(**4kb** gzipped)*
- Supports TypeScript.
2018-10-17 09:28:02 +08:00
- Reactive data-binding
2018-10-20 10:52:43 +08:00
- [Based on Shadow DOM](https://developers.google.cn/web/fundamentals/web-components/shadowdom)
- Easy to debug via [Omi DevTools Extension](https://github.com/f/omi-devtools) [[Install from Chrome WebStore](https://chrome.google.com/webstore/detail/omijs-devtools/pjgglfliglbhpcpalbpeloghnbceocmd/related)]
- Compliance with browser trend and API design.
- Merge **JSX** and **Web Components** into one framework.
- Web Components can also be a data-driven view, **`UI = fn(data)`**.
- JSX is the best development experience (code intelligent completion and tip) UI Expression with least [grammatical noise](https://github.com/facebook/jsx#why-not-template-literals).
- The original **Path Updating** system. Proxy-based automatic **accurate** update, **low power consumption**, high degree of freedom, excellent performance, easy integration of `requestIdleCallback`
- Say goodbye to `this.update` method when using **store system**! It will automatically update UI partially when data is changed.
- Look at [Facebook React vs Web Components](https://softwareengineering.stackexchange.com/questions/225400/pros-and-cons-of-facebooks-react-vs-web-components-polymer)Omi **combines their advantages** and gives developers the **freedom to choose the way they like**.
- **Shadow DOM merges with Virtual DOM**, Omi uses both virtual DOM and real Shadow DOM to make view updates more accurate and faster.
- With a Store system, 99.9% of projects don't need time travel, and not only Redux can travel, please don't come up on Redux, Omi store system can meet all projects
- **Scoped CSS**'s best solution is **Shadow DOM**, the community churning out frameworks and libraries for Scoped CSS (using JS or JSON writing styles such as Radium, jsxstyle, react-style; binding to webpack using generated unique `className` `filename-classname-hash`, such as CSS Modules, Vue), are hack technologies; *and Shadow DOM Style is the perfect solution*.
2018-10-15 10:19:45 +08:00
Compare TodoApp by Omi and React, Omi and React rendering DOM structure:
2018-10-14 14:02:19 +08:00
2018-10-20 10:52:43 +08:00
| **Omi** | **React** |
|-|-|
| ![Omi](./assets/omi-render.jpg) | ![React](./assets/react-render.jpg) |
2018-10-14 14:05:06 +08:00
2018-10-20 10:52:43 +08:00
On the left is Omi, the right side is React, and Omi uses Shadow DOM based style isolation and semantic structure.
2018-10-14 14:02:19 +08:00
2018-05-03 10:46:34 +08:00
---
2017-03-23 07:53:17 +08:00
2018-10-20 10:52:43 +08:00
- [Why Omi?](#why-omi)
2018-10-17 10:13:28 +08:00
- [Add Omi in One Minute](#add-omi-in-one-minute)
2018-05-03 10:46:34 +08:00
- [Getting Started](#getting-started)
2018-10-16 19:40:30 +08:00
- [Install](#install)
2018-10-20 10:52:43 +08:00
- [Hello Element](#hello-element)
2018-10-14 21:33:46 +08:00
- [TodoApp](#todoapp)
- [Store](#store)
2018-10-20 10:52:43 +08:00
- [Summary](#summary)
- [Lifecycle](#lifecycle)
2018-10-15 15:05:34 +08:00
- [Component Ecosystem](#component-ecosystem)
- [Browsers Support](#browsers-support)
2018-05-03 10:46:34 +08:00
- [Links](#links)
- [License](#license)
2018-10-20 10:52:43 +08:00
- [Contribution](#contribution)
2018-05-03 10:46:34 +08:00
2018-10-17 10:13:28 +08:00
## Add Omi in One Minute
2018-10-20 10:52:43 +08:00
This page demonstrates using Omi **with no build tooling**.
2018-10-17 10:13:28 +08:00
2018-10-17 12:59:27 +08:00
* [Online Demo!](https://tencent.github.io/omi/assets/)
* [Omi.js CDN](https://unpkg.com/omi)
2018-10-17 10:21:02 +08:00
2018-10-17 10:13:28 +08:00
```html
<!DOCTYPE html>
2018-10-17 10:13:28 +08:00
<html>
<head>
<meta charset="UTF-8" />
<title>Add Omi in One Minute</title>
</head>
<body>
2018-10-17 12:26:08 +08:00
<script src="https://unpkg.com/omi"></script>
2018-10-17 10:13:28 +08:00
<script>
2018-10-17 10:27:34 +08:00
const { WeElement, h, render, define } = Omi
2018-10-17 10:13:28 +08:00
2018-10-17 10:27:34 +08:00
class LikeButton extends WeElement {
2018-10-17 10:13:28 +08:00
install() {
this.data = { liked: false }
}
render() {
if (this.data.liked) {
return 'You liked this.'
}
return h(
'button',
{
onClick: () => {
this.data.liked = true
this.update()
}
},
'Like'
)
}
}
define('like-button', LikeButton)
render(h('like-button'), 'body')
</script>
</body>
</html>
```
2018-05-03 10:46:34 +08:00
2018-10-19 15:51:45 +08:00
You can also use `like-button` tag directly in HTML
```jsx
<body>
2018-10-20 10:52:43 +08:00
<like-button></like-button>
2018-10-19 15:51:45 +08:00
</body>
```
2018-05-03 10:46:34 +08:00
## Getting Started
2018-10-16 19:40:30 +08:00
### Install
```bash
2018-10-17 12:59:27 +08:00
$ npm i omi-cli -g # install cli
$ omi init your_project_name # init project, you can also exec 'omi init' in an empty folder
$ cd your_project_name # please ignore this command if you executed 'omi init' in an empty folder
$ npm start # develop
$ npm run build # release
2018-10-16 19:40:30 +08:00
```
2018-10-19 18:39:06 +08:00
Directory description:
```
├─ config
├─ public
├─ scripts
├─ src
│ ├─ assets
│ ├─ elements //Store all custom elements
│ ├─ store //Store all this store of pages
│ ├─ admin.js //Entry js of compilerwill build to admin.html
│ └─ index.js //Entry js of compilerwill build to index.html
```
2018-10-20 10:52:43 +08:00
CLI's auto-created project scaffolding is based on a single-page create-react-app to be converted into a multi-page one, with configuration issues to see [create-react-app user guide](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md)
2018-10-18 14:16:55 +08:00
2018-10-17 09:28:02 +08:00
### Hello Element
2017-03-23 07:53:17 +08:00
2018-10-20 10:52:43 +08:00
Define a custom element by extending **`WeElement`** base class and name it using **`@tag`** decorator:
2018-02-25 16:06:09 +08:00
2018-10-13 22:55:38 +08:00
```js
2018-10-16 00:11:10 +08:00
import { tag, WeElement, render } from 'omi'
2018-05-03 10:46:34 +08:00
2018-10-16 00:11:10 +08:00
@tag('hello-element')
2018-10-13 22:55:38 +08:00
class HelloElement extends WeElement {
onClick = (evt) => {
2018-10-20 10:52:43 +08:00
// trigger CustomEvent
2018-10-14 07:50:55 +08:00
this.fire('abc', { name : 'dntzhang', age: 12 })
2018-10-13 22:55:38 +08:00
evt.stopPropagation()
2018-02-25 16:06:09 +08:00
}
2018-10-13 22:55:38 +08:00
css() {
2018-10-20 10:52:43 +08:00
return `
div {
color: red;
cursor: pointer;
}`
2018-02-25 16:06:09 +08:00
}
2018-10-14 07:50:55 +08:00
render(props) {
2018-10-20 10:52:43 +08:00
return (
<div onClick={this.onClick}>
Hello {props.msg} {props.propFromParent}
<div>Click Me!</div>
</div>
)
}
2018-10-13 22:55:38 +08:00
}
```
2018-10-15 10:19:45 +08:00
Using `hello-element`:
2018-10-13 22:55:38 +08:00
``` js
2018-10-16 00:11:10 +08:00
import { tag, WeElement, render } from 'omi'
2018-10-13 22:55:38 +08:00
import './hello-element'
2018-10-16 00:11:10 +08:00
@tag('my-app')
2018-10-13 22:55:38 +08:00
class MyApp extends WeElement {
2018-10-20 10:52:43 +08:00
static get data() {
return { abc: '', passToChild: '' }
}
2018-10-14 07:50:55 +08:00
2018-10-20 10:52:43 +08:00
// bind CustomEvent
onAbc = (evt) => {
// get evt data by evt.detail
this.data.abc = ` by ${evt.detail.name}`
this.update()
}
2018-10-14 07:50:55 +08:00
2018-10-20 10:52:43 +08:00
css() {
return `
div {
color: green;
}`
}
2018-10-13 22:55:38 +08:00
2018-10-20 10:52:43 +08:00
render(props, data) {
return (
<div>
Hello {props.name} {data.abc}
<hello-element onAbc={this.onAbc} prop-from-parent={data.passToChild} msg="WeElement"></hello-element>
</div>
)
}
2018-02-25 16:06:09 +08:00
}
2018-10-14 07:50:55 +08:00
render(<my-app name='Omi v4.0'></my-app>, 'body')
2018-10-13 22:55:38 +08:00
```
2018-02-25 16:06:09 +08:00
2018-10-20 10:52:43 +08:00
Tell Babel to transform JSX into `Omi.h()` call:
2018-02-25 16:06:09 +08:00
2018-05-03 10:46:34 +08:00
``` json
{
2018-10-20 10:52:43 +08:00
"presets": ["env", "omi"]
2018-02-25 16:06:09 +08:00
}
2018-05-03 10:46:34 +08:00
```
2018-02-25 16:06:09 +08:00
2018-10-15 10:19:45 +08:00
The following two NPM packages need to be installed to support the above configuration:
2018-02-25 16:06:09 +08:00
2018-05-03 10:46:34 +08:00
``` bash
"babel-preset-env": "^1.6.0",
"babel-preset-omi": "^0.1.1",
```
2018-10-20 10:52:43 +08:00
If you don't want to write CSS in JS, you can use [to-string-loader](https://www.npmjs.com/package/to-string-loader),
2018-10-15 10:19:45 +08:00
For example, the following configuration:
2018-02-25 16:06:09 +08:00
2018-05-03 10:46:34 +08:00
``` js
2018-05-09 10:18:53 +08:00
{
2018-10-20 10:52:43 +08:00
test: /[\\|\/]_[\S]*\.css$/,
use: [
'to-string-loader',
'css-loader'
]
2018-05-09 10:18:53 +08:00
}
2018-05-03 10:46:34 +08:00
```
2018-10-20 10:52:43 +08:00
If your CSS file starts with "`_`", CSS will use `to-string-loader`., such as:
2018-05-09 10:18:53 +08:00
``` js
2018-10-16 00:11:10 +08:00
import { tag, WeElement render } from 'omi'
2018-10-20 10:52:43 +08:00
// typeof cssStr is string
2018-10-19 09:29:34 +08:00
import cssStr from './_index.css'
2018-05-09 10:18:53 +08:00
2018-10-16 00:11:10 +08:00
@tag('my-app')
2018-10-13 22:55:38 +08:00
class MyApp extends WeElement {
2018-05-09 10:18:53 +08:00
2018-10-13 22:55:38 +08:00
css() {
return cssStr
2018-05-09 10:18:53 +08:00
}
2018-10-14 07:50:55 +08:00
...
...
...
2018-05-09 10:18:53 +08:00
```
2018-10-14 13:47:23 +08:00
### TodoApp
2018-10-15 10:19:45 +08:00
Here is a relatively complete example of TodoApp:
2018-10-14 13:47:23 +08:00
```js
2018-10-16 00:11:10 +08:00
import { tag, WeElement, render } from 'omi'
2018-10-14 13:47:23 +08:00
2018-10-16 00:11:10 +08:00
@tag('todo-list')
2018-10-14 13:47:23 +08:00
class TodoList extends WeElement {
render(props) {
return (
<ul>
{props.items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
}
2018-10-16 00:11:10 +08:00
@tag('todo-app')
2018-10-14 13:47:23 +08:00
class TodoApp extends WeElement {
2018-10-20 10:52:43 +08:00
static get data() {
return { items: [], text: '' }
}
2018-10-14 13:47:23 +08:00
2018-10-20 10:52:43 +08:00
render() {
return (
<div>
<h3>TODO</h3>
<todo-list items={this.data.items} />
<form onSubmit={this.handleSubmit}>
<input
id="new-todo"
onChange={this.handleChange}
value={this.data.text}
/>
<button>
Add #{this.data.items.length + 1}
</button>
</form>
</div>
);
}
2018-10-14 13:47:23 +08:00
2018-10-20 10:52:43 +08:00
handleChange = (e) => {
this.data.text = e.target.value
}
2018-10-14 13:47:23 +08:00
2018-10-20 10:52:43 +08:00
handleSubmit = (e) => {
e.preventDefault();
if (!this.data.text.trim().length) {
return;
2018-10-14 13:47:23 +08:00
}
2018-10-20 10:52:43 +08:00
this.data.items.push({
text: this.data.text,
id: Date.now()
})
this.data.text = '';
this.update()
}
2018-10-14 13:47:23 +08:00
}
2018-10-20 10:52:43 +08:00
render(<todo-app/>, 'body')
2018-10-14 13:47:23 +08:00
```
2018-05-03 10:46:34 +08:00
2018-10-14 21:33:46 +08:00
### Store
2018-10-20 10:52:43 +08:00
Say goodbye to `this.update` method when using store system! It will automatically update the UI partially when data is changed. The powerful **Store architecture** is high-performanced since all the data is mounted on the store, except for components that rely on props to determine the state of the component.
2018-10-14 21:33:46 +08:00
```js
export default {
data: {
items: [],
text: '',
firstName: 'dnt',
lastName: 'zhang',
fullName: function () {
return this.firstName + this.lastName
},
2018-10-20 10:52:43 +08:00
globalPropTest: 'abc', // Change it will refresh all elements without changing the components and page declaring data dependency.
ccc: { ddd: 1 } // Change it will refresh all elements without changing the components and page declaring data dependency.
2018-10-14 21:33:46 +08:00
},
2018-10-15 10:19:45 +08:00
globalData: ['globalPropTest', 'ccc.ddd'],
2018-10-14 21:33:46 +08:00
add: function () {
2018-10-15 06:16:51 +08:00
if (!this.data.text.trim().length) {
2018-10-20 10:52:43 +08:00
return;
2018-10-15 06:12:33 +08:00
}
2018-10-14 21:33:46 +08:00
this.data.items.push({
text: this.data.text,
id: Date.now()
})
this.data.text = ''
2018-10-15 10:19:45 +08:00
}
2018-10-20 10:52:43 +08:00
// Default value is false, set to true will update all instances when data changing.
// updateAll: true
2018-10-14 21:33:46 +08:00
}
```
2018-10-15 10:19:45 +08:00
Custom Element requires declaring dependent data so that Omi stores compute the dependency path based on the data declared on the custom component and update it locally as needed. Such as:
2018-10-14 21:33:46 +08:00
```js
class TodoApp extends WeElement {
2018-10-20 10:52:43 +08:00
// If you use store, the data is only used to declare dependencies.
static get data() {
return { items: [], text: '' }
}
// ...
handleChange = (e) => {
this.store.data.text = e.target.value
}
2018-10-14 21:33:46 +08:00
2018-10-20 10:52:43 +08:00
handleSubmit = (e) => {
e.preventDefault()
this.store.add()
}
2018-10-14 21:33:46 +08:00
}
```
2018-10-20 10:52:43 +08:00
* The logic of data is **encapsulated in the store definition method** (such as `store.add`).
* Views are only **responsible for passing data to store**, such as calling `store.add` or setting `store.data.text` on top.
2018-10-15 06:12:33 +08:00
2018-10-20 10:52:43 +08:00
You need to inject `store` from the root node at render time to use this store:
2018-10-14 21:33:46 +08:00
```js
2018-10-20 10:52:43 +08:00
render(<todo-app/>, 'body', store)
2018-10-14 21:33:46 +08:00
```
2018-10-20 10:52:43 +08:00
[→ Store Source Code](https://github.com/Tencent/omi/blob/master/packages/omi/examples/store/main.js)
2018-10-15 10:19:45 +08:00
2018-10-15 06:24:55 +08:00
2018-10-20 10:52:43 +08:00
#### Summary
2018-10-14 21:57:20 +08:00
2018-10-20 10:52:43 +08:00
* `store.data` is used to list all attributes and default values (except the components of the view decided by props).
* The data of the component and page is used to list the attributes of the dependent store.data *(Omi will record path)* and update on demand.
* If there are few simple components on the page, `updateAll` can be set to `true`, and components and pages don't need to declare data, and they don't update on demand
* The path declared in `globalData` refreshes all pages and components by modifying the value of the corresponding path, which can be used to list all pages or most of the public properties path
2018-10-14 21:57:20 +08:00
2018-05-03 10:46:34 +08:00
### Lifecycle
2018-02-25 16:06:09 +08:00
2018-10-20 10:52:43 +08:00
| Lifecycle method | When it gets called |
|-|-|
| `install` | before the component gets mounted to the DOM |
| `installed` | after the component gets mounted to the DOM |
| `uninstall` | prior to removal from the DOM |
| `beforeUpdate` | before `render()` |
| `afterUpdate` | after `render()` |
2017-03-23 07:53:17 +08:00
2018-10-15 15:05:34 +08:00
## Component Ecosystem
* [https://www.webcomponents.org/](https://www.webcomponents.org/)
* [https://www.webcomponents.org/elements](https://www.webcomponents.org/elements)
2018-10-18 17:14:09 +08:00
I believe you can easily convert web components elements to omi elements.
2018-10-15 15:05:34 +08:00
## Browsers Support
2018-10-15 11:53:32 +08:00
2018-10-18 21:18:17 +08:00
Omi 4.0+ works in the latest two versions of all major browsers: Safari 10+, IE 11+, and the evergreen Chrome, Firefox, and Edge.
2018-10-15 11:53:32 +08:00
![Browsers Support](./assets/browsers-support.png)
2018-10-20 10:52:43 +08:00
[→ Polyfills](https://github.com/webcomponents/webcomponentsjs)
2018-10-15 11:53:32 +08:00
2018-10-20 10:52:43 +08:00
> If you want to be compatible with IE11, use the Omi file of [→ this project](https://github.com/Tencent/omi/tree/master/packages/omi-ie11). This project uses JSON Diff + Timer instead of Proxy.
2018-10-19 08:38:03 +08:00
2018-10-20 10:52:43 +08:00
> You can dynamically load the JS of this project in the IE9 environment, and the proxy version is still used in other environments.
2018-10-18 23:06:08 +08:00
2018-05-03 10:46:34 +08:00
## Links
2017-03-23 07:53:17 +08:00
2018-05-03 11:19:37 +08:00
- [omijs.org](http://omijs.org/)
2018-10-20 08:45:50 +08:00
- [Omi.js DevTools](https://github.com/f/omi-devtools)
2017-03-23 07:53:17 +08:00
2018-05-03 10:46:34 +08:00
## License
2017-03-09 16:26:06 +08:00
2018-10-19 09:29:34 +08:00
MIT © Tencent
2018-10-14 08:23:31 +08:00
2018-10-20 10:52:43 +08:00
## Contribution
2018-10-20 10:56:09 +08:00
## Contributing
1. Fork it (https://github.com/Tencent/omi/fork)
2. Create your branch (`git checkout -b my-urgent-hotfix`)
3. Commit your changes (`git commit -am 'Fixed something'`)
4. Push to the branch (`git push origin my-urgent-hotfix`)
5. Create a new Pull Request
2018-10-20 10:52:43 +08:00
Please contact us for any questions:
* [@f](https://github.com/f)
* [@dntzhang](https://github.com/dntzhang)