简体中文 | English
Web Components engine based on VDOM, JSX, MobX & TypeScript
feature WebCell 3 WebCell 2 React Vue JS language TypeScript 5 TypeScript 4 ECMAScript or TypeScript ECMAScript or TypeScript JS syntax ES decorator stage-3 ES decorator stage-2 XML syntax JSX import JSX factory JSX factory/import HTML/Vue template or JSX (optional) DOM API Web components Web components HTML 5+ HTML 5+ view renderer DOM Renderer 2 SnabbDOM (built-in) SnabbDOM (forked) state API MobX@observable
this.state
this.state
or useState()
this.$data
or ref()
props API MobX @observable
@watch
this.props
or props => {}
this.$props
or defineProps()
state manager MobX 6+ MobX 4/5 Redux VueX page router JSX tags JSX tags + JSON data JSX tags JSON data asset bundler Parcel 2 Parcel 1 webpack Vite
npm install dom-renderer mobx web-cell
npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D
{ "scripts": { "start": "parcel source/index.html --open", "build": "parcel build source/index.html --public-url ." } }
{ "compilerOptions": { "target": "ES6", "module": "ES2020", "moduleResolution": "Node", "useDefineForClassFields": true, "jsx": "react-jsx", "jsxImportSource": "dom-renderer" } }
{ "extends": "@parcel/config-default", "transformers": { "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"] } }
<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script> <script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script> <script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script> <script src="source/MyTag.tsx"></script> <my-tag></my-tag>
import { DOMRenderer } from 'dom-renderer'; import { FC, PropsWithChildren } from 'web-cell'; const Hello: FC<PropsWithChildren> = ({ children = 'World' }) => ( <h1>Hello, {children}!</h1> ); new DOMRenderer().render(<Hello>WebCell</Hello>);
import { DOMRenderer } from 'dom-renderer'; import { component } from 'web-cell'; @component({ tagName: 'hello-world', mode: 'open' }) class Hello extends HTMLElement { render() { return ( <h1> Hello, <slot />! </h1> ); } } new DOMRenderer().render( <> <Hello>WebCell</Hello> {/* or */} <hello-world>WebCell</hello-world> </> );
import { DOMRenderer } from 'dom-renderer'; import { observable } from 'mobx'; import { WebCell, component, attribute, observer } from 'web-cell'; interface HelloProps { name?: string; } interface Hello extends WebCell<HelloProps> {} @component({ tagName: 'hello-world' }) @observer class Hello extends HTMLElement implements WebCell<HelloProps> { @attribute @observable accessor name = ''; render() { return <h1>Hello, {this.name}!</h1>; } } new DOMRenderer().render(<Hello name="WebCell" />); // or for HTML tag props in TypeScript declare global { namespace JSX { interface IntrinsicElements { 'hello-world': HelloProps; } } } new DOMRenderer().render(<hello-world name="WebCell" />);
import { DOMRenderer } from 'dom-renderer'; import { observable } from 'mobx'; import { FC, observer } from 'web-cell'; class CounterModel { @observable accessor times = 0; } const couterStore = new CounterModel(); const Counter: FC = observer(() => ( <button onClick={() => (couterStore.times += 1)}> Counts: {couterStore.times} </button> )); new DOMRenderer().render(<Counter />);
import { DOMRenderer } from 'dom-renderer'; import { observable } from 'mobx'; import { component, observer } from 'web-cell'; @component({ tagName: 'my-counter' }) @observer class Counter extends HTMLElement { @observable accessor times = 0; handleClick = () => (this.times += 1); render() { return <button onClick={this.handleClick}>Counts: {this.times}</button>; } } new DOMRenderer().render(<Counter />);
import { component } from 'web-cell'; import { stringifyCSS } from 'web-utility'; @component({ tagName: 'my-button', mode: 'open' }) export class MyButton extends HTMLElement { style = stringifyCSS({ '.btn': { color: 'white', background: 'lightblue' } }); render() { return ( <> <style>{this.style}</style> <a className="btn"> <slot /> </a> </> ); } }
import { component } from 'web-cell'; @component({ tagName: 'my-button', mode: 'open' }) export class MyButton extends HTMLElement { render() { return ( <> <link rel="stylesheet" href="https://unpkg.com/bootstrap@5.3.6/dist/css/bootstrap.min.css" /> <a className="btn"> <slot /> </a> </> ); } }
.btn { color: white; background: lightblue; }
import { WebCell, component } from 'web-cell'; import styles from './scoped.css' assert { type: 'css' }; interface MyButton extends WebCell {} @component({ tagName: 'my-button', mode: 'open' }) export class MyButton extends HTMLElement implements WebCell { connectedCallback() { this.root.adoptedStyleSheets = [styles]; } render() { return ( <a className="btn"> <slot /> </a> ); } }
import { component, on } from 'web-cell'; @component({ tagName: 'my-table' }) export class MyTable extends HTMLElement { @on('click', ':host td > button') handleEdit(event: MouseEvent, { dataset: { id } }: HTMLButtonElement) { console.log(`editing row: ${id}`); } render() { return ( <table> <tr> <td>1</td> <td>A</td> <td> <button data-id="1">edit</button> </td> </tr> <tr> <td>2</td> <td>B</td> <td> <button data-id="2">edit</button> </td> </tr> <tr> <td>3</td> <td>C</td> <td> <button data-id="3">edit</button> </td> </tr> </table> ); } }
import { observable } from 'mobx'; import { component, observer, reaction } from 'web-cell'; @component({ tagName: 'my-counter' }) @observer export class Counter extends HTMLElement { @observable accessor times = 0; handleClick = () => (this.times += 1); @reaction(({ times }) => times) echoTimes(newValue: number, oldValue: number) { console.log(`newValue: ${newValue}, oldValue: ${oldValue}`); } render() { return <button onClick={this.handleClick}>Counts: {this.times}</button>; } }
import { DOMRenderer } from 'dom-renderer'; import { WebField, component, formField, observer } from 'web-cell'; interface MyField extends WebField {} @component({ tagName: 'my-field', mode: 'open' }) @formField @observer class MyField extends HTMLElement implements WebField { render() { const { name } = this; return ( <input name={name} onChange={({ currentTarget: { value } }) => (this.value = value) } /> ); } } new DOMRenderer().render( <form method="POST" action="/api/data"> <MyField name="test" /> <button>submit</button> </form> );
import { FC } from 'web-cell'; const AsyncTag: FC = () => <div>Async</div>; export default AsyncTag;
import { DOMRenderer } from 'dom-renderer'; import { lazy } from 'web-cell'; const AsyncTag = lazy(() => import('./AsyncTag')); new DOMRenderer().render(<AsyncTag />);Async rendering (experimental)
import { DOMRenderer } from 'dom-renderer'; new DOMRenderer().render( <a> <b>Async rendering</b> </a>, document.body, 'async' );
import { component } from 'web-cell'; @component({ tagName: 'async-renderer', renderMode: 'async' }) export class AsyncRenderer extends HTMLElement { render() { return ( <a> <b>Async rendering</b> </a> ); } }
import { DOMRenderer } from 'dom-renderer'; import { AnimateCSS } from 'web-cell'; new DOMRenderer().render( <AnimateCSS type="fadeIn" component={props => <h1 {...props}>Fade In</h1>} /> );
import 'web-cell/polyfill';
https://github.com/EasyWebApp/DOM-Renderer?tab=readme-ov-file#nodejs--bun
connectedCallback
disconnectedCallback
attributeChangedCallback
adoptedCallback
updatedCallback
mountedCallback
formAssociatedCallback
formDisabledCallback
formResetCallback
formStateRestoreCallback
We recommend these libraries to use with WebCell:
State management: MobX (also powered by TypeScript & Decorator)
Router: Cell Router
UI components
HTTP request: KoAJAX (based on Koa-like middlewares)
Utility: Web utility methods & types
Event stream: Iterable Observer (Observable
proposal)
MarkDown integration: Parcel MDX transformer (MDX Compiler plugin)
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4