WebCell
    Preparing search index...

    WebCell

    WebCell

    WebCell logo

    简体中文 | English

    Web Components engine based on VDOM, JSX, MobX & TypeScript

    NPM Dependency CI & CD

    Anti 996 license UI library recommendation list

    Slideshow Gitter

    Edit WebCell demo

    NPM

    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
    

    Demo & GitHub template

    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/[email protected]/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 { DOMRenderer } from 'dom-renderer';
    import { observer, PropsWithChildren } from 'web-cell';
    import { sleep } from 'web-utility';

    const AsyncComponent = observer(async ({ children }: PropsWithChildren) => {
    await sleep(1);

    return <p>Async Component in {children}</p>;
    });

    new DOMRenderer().render(<AsyncComponent>WebCell</AsyncComponent>);
    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 />);
    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>} />
    );
    npm install jsdom
    
    import 'web-cell/polyfill';
    

    https://github.com/EasyWebApp/DOM-Renderer?tab=readme-ov-file#nodejs--bun

    1. connectedCallback
    2. disconnectedCallback
    3. attributeChangedCallback
    4. adoptedCallback
    5. updatedCallback
    6. mountedCallback
    7. formAssociatedCallback
    8. formDisabledCallback
    9. formResetCallback
    10. formStateRestoreCallback
    1. Basic
    2. DashBoard
    3. Mobile
    4. Static site

    We recommend these libraries to use with WebCell:

    1. Development contribution