MCPcopy Index your code
hub / github.com/EasyWebApp/WebCell

github.com/EasyWebApp/WebCell @v3.2.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.2.2 ↗ · + Follow
111 symbols 206 edges 21 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

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][12]

Feature

Engines comparison

feature WebCell 3 WebCell 2 React Vue
JS language [TypeScript 5][13] TypeScript 4 ECMAScript or TypeScript ECMAScript or TypeScript
JS syntax [ES decorator stage-3][14] ES decorator stage-2
XML syntax [JSX import][15] JSX factory JSX factory/import HTML/Vue template or JSX (optional)
DOM API [Web components][16] Web components HTML 5+ HTML 5+
view renderer [DOM Renderer 2][17] SnabbDOM (built-in) SnabbDOM (forked)
state API [MobX @observable][18] 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+][19] MobX 4/5 Redux VueX
page router [JSX][20] tags JSX tags + JSON data JSX tags JSON data
asset bundler [Parcel 2][21] Parcel 1 webpack Vite

Installation

npm install dom-renderer mobx web-cell

Web browser usage

[Demo & GitHub template][22]

Project bootstrap

Tool chain

npm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D

package.json

{
    "scripts": {
        "start": "parcel source/index.html --open",
        "build": "parcel build source/index.html --public-url ."
    }
}

tsconfig.json

{
    "compilerOptions": {
        "target": "ES6",
        "module": "ES2020",
        "moduleResolution": "Node",
        "useDefineForClassFields": true,
        "jsx": "react-jsx",
        "jsxImportSource": "dom-renderer"
    }
}

.parcelrc

{
    "extends": "@parcel/config-default",
    "transformers": {
        "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
    }
}

source/index.html

<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="https://github.com/EasyWebApp/WebCell/raw/v3.2.2/source/MyTag.tsx"></script>

<my-tag></my-tag>

Function component

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>);

Class component

Children slot

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>
    </>
);

DOM Props

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" />);

Inner state

Function component

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 />);

Class component

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 />);

CSS scope

Inline style

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>
            </>
        );
    }
}

Link stylesheet

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>
            </>
        );
    }
}

CSS module

scoped.css
.btn {
    color: white;
    background: lightblue;
}
MyButton.tsx
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>
        );
    }
}

Event delegation

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>
        );
    }
}

MobX reaction

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>;
    }
}

Form association

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>
);

Async component

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 

Async Component in {children}

;
});

new DOMRenderer().render(<AsyncComponent>WebCell</AsyncComponent>);

Async loading

AsyncTag.tsx

import { FC } from 'web-cell';

const AsyncTag: FC = () => 

Async

;

export default AsyncTag;

index.tsx

import { DOMRenderer } from 'dom-renderer';
import { lazy } from 'web-cell';

const AsyncTag = lazy(() => import('./AsyncTag'));

new DOMRenderer().render(<AsyncTag />);

Async rendering (experimental)

DOM tree

import { DOMRenderer } from 'dom-renderer';

new DOMRenderer().render(
    <a>
        <b>Async rendering</b>
    </a>,
    document.body,
    'async'
);

Class component

import { component } from 'web-cell';

@component({
    tagName: 'async-renderer',
    renderMode: 'async'
})
export class AsyncRenderer extends HTMLElement {
    render() {
        return (
            <a>
                <b>Async rendering</b>
            </a>
        );
    }
}

Animate CSS component

import { DOMRenderer } from 'dom-renderer';
import { AnimateCSS } from 'web-cell';

new DOMRenderer().render(
    <AnimateCSS type="fadeIn" component={props => <h1 {...props}>Fade In</h1>} />
);

Node.js usage

Tool chain

npm install jsdom

Polyfill

import 'web-cell/polyfill';

Server Side Rendering

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

Basic knowledge

  • [Web components][23]
  • [Custom elements][24]
  • [Shadow DOM][25]
  • [Element Internals][26]
  • [CSS variables][27]
  • [View transitions][28]
  • [ECMAScript 6+][29]
  • TypeScript 5+

Life Cycle hooks

  1. [connectedCallback][30]
  2. [disconnectedCallback][31]
  3. [attributeChangedCallback][32]
  4. [adoptedCallback][33]
  5. [updatedCallback][34]
  6. [mountedCallback][35]
  7. [formAssociatedCallback][36]
  8. [formDisabledCallback][37]
  9. [formResetCallback][38]
  10. [formStateRestoreCallback][39]

Scaffolds

  1. [Basic][22]
  2. [DashBoard][40]
  3. [Mobile][41]
  4. [Static site][42]

Ecosystem

We recommend these libraries to use with WebCell:

  • State management: MobX (also powered by TypeScript & Decorator)
  • Router: [Cell Router][43]
  • UI components

    • [BootCell][44] (based on BootStrap v5)
    • [MDUI][45] (based on Material Design v3)
    • [GitHub Web Widget][46]
  • HTTP request: [KoAJAX][47] (based on Koa-like middlewares)

  • Utility: [Web utility][48] methods & types
  • Event stream: [Iterable Observer][49] (Observable proposal)
  • MarkDown integration: [Parcel MDX transformer][50] (MDX Compiler plugin)

Roadmap

  • [x] [Server-side Render][51]
  • [x] [Async Component loading][52]

v2 to v3 migration

More guides

  1. Development contribution

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Method 42
Class 38
Interface 17
Function 14

Languages

TypeScript100%

Modules by API surface

test/WebCell.spec.tsx22 symbols
source/decorator.tsx14 symbols
source/WebCell.tsx14 symbols
source/WebField.ts13 symbols
preview/Clock.tsx11 symbols
test/MobX.spec.tsx8 symbols
source/Animation/index.tsx7 symbols
source/Async.tsx6 symbols
test/WebField.spec.tsx4 symbols
source/utility.ts4 symbols
preview/Field.tsx4 symbols
preview/Home.tsx2 symbols

For agents

$ claude mcp add WebCell \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page