{"id":14990296,"url":"https://github.com/easywebapp/webcell","last_synced_at":"2025-05-16T11:04:36.009Z","repository":{"id":44656122,"uuid":"142104909","full_name":"EasyWebApp/WebCell","owner":"EasyWebApp","description":"Web Components engine based on VDOM, JSX, MobX \u0026 TypeScript","archived":false,"fork":false,"pushed_at":"2025-03-14T13:40:47.000Z","size":5434,"stargazers_count":177,"open_issues_count":6,"forks_count":16,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-13T22:38:09.227Z","etag":null,"topics":["decorator","ecmascript","jsx","mobx","mvvm","spa","typescript","vdom","web-component","webapp"],"latest_commit_sha":null,"homepage":"https://web-cell.dev/WebCell/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EasyWebApp.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"License","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"custom":["https://paypal.me/TechQuery","https://tech-query.me/image/TechQuery-Alipay.jpg"]}},"created_at":"2018-07-24T04:37:57.000Z","updated_at":"2025-04-14T10:09:52.000Z","dependencies_parsed_at":"2022-07-24T21:02:18.857Z","dependency_job_id":"f75db9a3-8372-4c05-bbbd-9b7d3a59232a","html_url":"https://github.com/EasyWebApp/WebCell","commit_stats":{"total_commits":75,"total_committers":4,"mean_commits":18.75,"dds":0.2533333333333333,"last_synced_commit":"a9f0db470c6b7b41c89a38c78148ec1f95a35b30"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EasyWebApp%2FWebCell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EasyWebApp%2FWebCell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EasyWebApp%2FWebCell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EasyWebApp%2FWebCell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EasyWebApp","download_url":"https://codeload.github.com/EasyWebApp/WebCell/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254518384,"owners_count":22084374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decorator","ecmascript","jsx","mobx","mvvm","spa","typescript","vdom","web-component","webapp"],"created_at":"2024-09-24T14:19:51.013Z","updated_at":"2025-05-16T11:04:35.987Z","avatar_url":"https://github.com/EasyWebApp.png","language":"TypeScript","readme":"# WebCell\n\n![WebCell logo](https://web-cell.dev/WebCell-0.f9823b00.png)\n\n[简体中文](./guide/ReadMe-zh.md) | English\n\n[Web Components][1] engine based on VDOM, [JSX][2], [MobX][3] \u0026 [TypeScript][4]\n\n[![NPM Dependency](https://img.shields.io/librariesio/github/EasyWebApp/WebCell.svg)][5]\n[![CI \u0026 CD](https://github.com/EasyWebApp/WebCell/actions/workflows/main.yml/badge.svg)][6]\n\n[![Anti 996 license](https://img.shields.io/badge/license-Anti%20996-blue.svg)][7]\n[![UI library recommendation list](https://jaywcjlove.github.io/sb/ico/awesome.svg)][8]\n\n[![Slideshow](https://img.shields.io/badge/learn-Slideshow-blue)][9]\n[![Gitter](https://badges.gitter.im/EasyWebApp/community.svg)][10]\n\n[![Edit WebCell demo](https://codesandbox.io/static/img/play-codesandbox.svg)][11]\n\n[![NPM](https://nodei.co/npm/web-cell.png?downloads=true\u0026downloadRank=true\u0026stars=true)][12]\n\n## Feature\n\n### Engines comparison\n\n|    feature    |         WebCell 3          |      WebCell 2       |             React             |                 Vue                 |\n| :-----------: | :------------------------: | :------------------: | :---------------------------: | :---------------------------------: |\n|  JS language  |     [TypeScript 5][13]     |     TypeScript 4     |   ECMAScript or TypeScript    |      ECMAScript or TypeScript       |\n|   JS syntax   | [ES decorator stage-3][14] | ES decorator stage-2 |                               |                                     |\n|  XML syntax   |      [JSX import][15]      |     JSX factory      |      JSX factory/import       | HTML/Vue template or JSX (optional) |\n|    DOM API    |    [Web components][16]    |    Web components    |            HTML 5+            |               HTML 5+               |\n| view renderer |    [DOM Renderer 2][17]    |       SnabbDOM       |          (built-in)           |          SnabbDOM (forked)          |\n|   state API   |  [MobX `@observable`][18]  |     `this.state`     | `this.state` or `useState()`  |       `this.$data` or `ref()`       |\n|   props API   |     MobX `@observable`     |       `@watch`       | `this.props` or `props =\u003e {}` |  `this.$props` or `defineProps()`   |\n| state manager |       [MobX 6+][19]        |       MobX 4/5       |             Redux             |                VueX                 |\n|  page router  |       [JSX][20] tags       | JSX tags + JSON data |           JSX tags            |              JSON data              |\n| asset bundler |       [Parcel 2][21]       |       Parcel 1       |            webpack            |                Vite                 |\n\n## Installation\n\n```shell\nnpm install dom-renderer mobx web-cell\n```\n\n## Web browser usage\n\n[Demo \u0026 **GitHub template**][22]\n\n### Project bootstrap\n\n#### Tool chain\n\n```shell\nnpm install parcel @parcel/config-default @parcel/transformer-typescript-tsc -D\n```\n\n#### `package.json`\n\n```json\n{\n    \"scripts\": {\n        \"start\": \"parcel source/index.html --open\",\n        \"build\": \"parcel build source/index.html --public-url .\"\n    }\n}\n```\n\n#### `tsconfig.json`\n\n```json\n{\n    \"compilerOptions\": {\n        \"target\": \"ES6\",\n        \"module\": \"ES2020\",\n        \"moduleResolution\": \"Node\",\n        \"useDefineForClassFields\": true,\n        \"jsx\": \"react-jsx\",\n        \"jsxImportSource\": \"dom-renderer\"\n    }\n}\n```\n\n#### `.parcelrc`\n\n```json\n{\n    \"extends\": \"@parcel/config-default\",\n    \"transformers\": {\n        \"*.{ts,tsx}\": [\"@parcel/transformer-typescript-tsc\"]\n    }\n}\n```\n\n#### `source/index.html`\n\n```html\n\u003cscript src=\"https://polyfill.web-cell.dev/feature/ECMAScript.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://polyfill.web-cell.dev/feature/WebComponents.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://polyfill.web-cell.dev/feature/ElementInternals.js\"\u003e\u003c/script\u003e\n\n\u003cscript src=\"source/MyTag.tsx\"\u003e\u003c/script\u003e\n\n\u003cmy-tag\u003e\u003c/my-tag\u003e\n```\n\n### Function component\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { FC, PropsWithChildren } from 'web-cell';\n\nconst Hello: FC\u003cPropsWithChildren\u003e = ({ children = 'World' }) =\u003e (\n    \u003ch1\u003eHello, {children}!\u003c/h1\u003e\n);\n\nnew DOMRenderer().render(\u003cHello\u003eWebCell\u003c/Hello\u003e);\n```\n\n### Class component\n\n#### Children slot\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { component } from 'web-cell';\n\n@component({\n    tagName: 'hello-world',\n    mode: 'open'\n})\nclass Hello extends HTMLElement {\n    render() {\n        return (\n            \u003ch1\u003e\n                Hello, \u003cslot /\u003e!\n            \u003c/h1\u003e\n        );\n    }\n}\n\nnew DOMRenderer().render(\n    \u003c\u003e\n        \u003cHello\u003eWebCell\u003c/Hello\u003e\n        {/* or */}\n        \u003chello-world\u003eWebCell\u003c/hello-world\u003e\n    \u003c/\u003e\n);\n```\n\n#### DOM Props\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { observable } from 'mobx';\nimport { WebCell, component, attribute, observer } from 'web-cell';\n\ninterface HelloProps {\n    name?: string;\n}\n\ninterface Hello extends WebCell\u003cHelloProps\u003e {}\n\n@component({ tagName: 'hello-world' })\n@observer\nclass Hello extends HTMLElement implements WebCell\u003cHelloProps\u003e {\n    @attribute\n    @observable\n    accessor name = '';\n\n    render() {\n        return \u003ch1\u003eHello, {this.name}!\u003c/h1\u003e;\n    }\n}\n\nnew DOMRenderer().render(\u003cHello name=\"WebCell\" /\u003e);\n\n// or for HTML tag props in TypeScript\n\ndeclare global {\n    namespace JSX {\n        interface IntrinsicElements {\n            'hello-world': HelloProps;\n        }\n    }\n}\nnew DOMRenderer().render(\u003chello-world name=\"WebCell\" /\u003e);\n```\n\n### Inner state\n\n#### Function component\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { observable } from 'mobx';\nimport { FC, observer } from 'web-cell';\n\nclass CounterModel {\n    @observable\n    accessor times = 0;\n}\n\nconst couterStore = new CounterModel();\n\nconst Counter: FC = observer(() =\u003e (\n    \u003cbutton onClick={() =\u003e (couterStore.times += 1)}\u003e\n        Counts: {couterStore.times}\n    \u003c/button\u003e\n));\n\nnew DOMRenderer().render(\u003cCounter /\u003e);\n```\n\n#### Class component\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { observable } from 'mobx';\nimport { component, observer } from 'web-cell';\n\n@component({ tagName: 'my-counter' })\n@observer\nclass Counter extends HTMLElement {\n    @observable\n    accessor times = 0;\n\n    handleClick = () =\u003e (this.times += 1);\n\n    render() {\n        return \u003cbutton onClick={this.handleClick}\u003eCounts: {this.times}\u003c/button\u003e;\n    }\n}\n\nnew DOMRenderer().render(\u003cCounter /\u003e);\n```\n\n### CSS scope\n\n#### Inline style\n\n```tsx\nimport { component } from 'web-cell';\nimport { stringifyCSS } from 'web-utility';\n\n@component({\n    tagName: 'my-button',\n    mode: 'open'\n})\nexport class MyButton extends HTMLElement {\n    style = stringifyCSS({\n        '.btn': {\n            color: 'white',\n            background: 'lightblue'\n        }\n    });\n\n    render() {\n        return (\n            \u003c\u003e\n                \u003cstyle\u003e{this.style}\u003c/style\u003e\n\n                \u003ca className=\"btn\"\u003e\n                    \u003cslot /\u003e\n                \u003c/a\u003e\n            \u003c/\u003e\n        );\n    }\n}\n```\n\n#### Link stylesheet\n\n```tsx\nimport { component } from 'web-cell';\n\n@component({\n    tagName: 'my-button',\n    mode: 'open'\n})\nexport class MyButton extends HTMLElement {\n    render() {\n        return (\n            \u003c\u003e\n                \u003clink\n                    rel=\"stylesheet\"\n                    href=\"https://unpkg.com/bootstrap@5.3.3/dist/css/bootstrap.min.css\"\n                /\u003e\n                \u003ca className=\"btn\"\u003e\n                    \u003cslot /\u003e\n                \u003c/a\u003e\n            \u003c/\u003e\n        );\n    }\n}\n```\n\n#### CSS module\n\n##### `scoped.css`\n\n```css\n.btn {\n    color: white;\n    background: lightblue;\n}\n```\n\n##### `MyButton.tsx`\n\n```tsx\nimport { WebCell, component } from 'web-cell';\n\nimport styles from './scoped.css' assert { type: 'css' };\n\ninterface MyButton extends WebCell {}\n\n@component({\n    tagName: 'my-button',\n    mode: 'open'\n})\nexport class MyButton extends HTMLElement implements WebCell {\n    connectedCallback() {\n        this.root.adoptedStyleSheets = [styles];\n    }\n\n    render() {\n        return (\n            \u003ca className=\"btn\"\u003e\n                \u003cslot /\u003e\n            \u003c/a\u003e\n        );\n    }\n}\n```\n\n### Event delegation\n\n```tsx\nimport { component, on } from 'web-cell';\n\n@component({ tagName: 'my-table' })\nexport class MyTable extends HTMLElement {\n    @on('click', ':host td \u003e button')\n    handleEdit(event: MouseEvent, { dataset: { id } }: HTMLButtonElement) {\n        console.log(`editing row: ${id}`);\n    }\n\n    render() {\n        return (\n            \u003ctable\u003e\n                \u003ctr\u003e\n                    \u003ctd\u003e1\u003c/td\u003e\n                    \u003ctd\u003eA\u003c/td\u003e\n                    \u003ctd\u003e\n                        \u003cbutton data-id=\"1\"\u003eedit\u003c/button\u003e\n                    \u003c/td\u003e\n                \u003c/tr\u003e\n                \u003ctr\u003e\n                    \u003ctd\u003e2\u003c/td\u003e\n                    \u003ctd\u003eB\u003c/td\u003e\n                    \u003ctd\u003e\n                        \u003cbutton data-id=\"2\"\u003eedit\u003c/button\u003e\n                    \u003c/td\u003e\n                \u003c/tr\u003e\n                \u003ctr\u003e\n                    \u003ctd\u003e3\u003c/td\u003e\n                    \u003ctd\u003eC\u003c/td\u003e\n                    \u003ctd\u003e\n                        \u003cbutton data-id=\"3\"\u003eedit\u003c/button\u003e\n                    \u003c/td\u003e\n                \u003c/tr\u003e\n            \u003c/table\u003e\n        );\n    }\n}\n```\n\n### MobX reaction\n\n```tsx\nimport { observable } from 'mobx';\nimport { component, observer, reaction } from 'web-cell';\n\n@component({ tagName: 'my-counter' })\n@observer\nexport class Counter extends HTMLElement {\n    @observable\n    accessor times = 0;\n\n    handleClick = () =\u003e (this.times += 1);\n\n    @reaction(({ times }) =\u003e times)\n    echoTimes(newValue: number, oldValue: number) {\n        console.log(`newValue: ${newValue}, oldValue: ${oldValue}`);\n    }\n\n    render() {\n        return \u003cbutton onClick={this.handleClick}\u003eCounts: {this.times}\u003c/button\u003e;\n    }\n}\n```\n\n### Form association\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { WebField, component, formField, observer } from 'web-cell';\n\ninterface MyField extends WebField {}\n\n@component({\n    tagName: 'my-field',\n    mode: 'open'\n})\n@formField\n@observer\nclass MyField extends HTMLElement implements WebField {\n    render() {\n        const { name } = this;\n\n        return (\n            \u003cinput\n                name={name}\n                onChange={({ currentTarget: { value } }) =\u003e\n                    (this.value = value)\n                }\n            /\u003e\n        );\n    }\n}\n\nnew DOMRenderer().render(\n    \u003cform method=\"POST\" action=\"/api/data\"\u003e\n        \u003cMyField name=\"test\" /\u003e\n\n        \u003cbutton\u003esubmit\u003c/button\u003e\n    \u003c/form\u003e\n);\n```\n\n### Async component\n\n#### `AsyncTag.tsx`\n\n```tsx\nimport { FC } from 'web-cell';\n\nconst AsyncTag: FC = () =\u003e \u003cdiv\u003eAsync\u003c/div\u003e;\n\nexport default AsyncTag;\n```\n\n#### `index.tsx`\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { lazy } from 'web-cell';\n\nconst AsyncTag = lazy(() =\u003e import('./AsyncTag'));\n\nnew DOMRenderer().render(\u003cAsyncTag /\u003e);\n```\n\n### Async rendering (experimental)\n\n#### DOM tree\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\n\nnew DOMRenderer().render(\n    \u003ca\u003e\n        \u003cb\u003eAsync rendering\u003c/b\u003e\n    \u003c/a\u003e,\n    document.body,\n    'async'\n);\n```\n\n#### Class component\n\n```tsx\nimport { component } from 'web-cell';\n\n@component({\n    tagName: 'async-renderer',\n    renderMode: 'async'\n})\nexport class AsyncRenderer extends HTMLElement {\n    render() {\n        return (\n            \u003ca\u003e\n                \u003cb\u003eAsync rendering\u003c/b\u003e\n            \u003c/a\u003e\n        );\n    }\n}\n```\n\n### Animate CSS component\n\n```tsx\nimport { DOMRenderer } from 'dom-renderer';\nimport { AnimateCSS } from 'web-cell';\n\nnew DOMRenderer().render(\n    \u003cAnimateCSS\n        type=\"fadeIn\"\n        component={props =\u003e \u003ch1 {...props}\u003eFade In\u003c/h1\u003e}\n    /\u003e\n);\n```\n\n## Node.js usage\n\n### Tool chain\n\n```shell\nnpm install jsdom\n```\n\n### Polyfill\n\n```js\nimport 'web-cell/polyfill';\n```\n\n### Server Side Rendering\n\nhttps://github.com/EasyWebApp/DOM-Renderer?tab=readme-ov-file#nodejs--bun\n\n## Basic knowledge\n\n-   [Web components][23]\n-   [Custom elements][24]\n-   [Shadow DOM][25]\n-   [Element Internals][26]\n-   [CSS variables][27]\n-   [View transitions][28]\n-   [ECMAScript 6+][29]\n-   [TypeScript 5+][4]\n\n## Life Cycle hooks\n\n1.  [`connectedCallback`][30]\n2.  [`disconnectedCallback`][31]\n3.  [`attributeChangedCallback`][32]\n4.  [`adoptedCallback`][33]\n5.  [`updatedCallback`][34]\n6.  [`mountedCallback`][35]\n7.  [`formAssociatedCallback`][36]\n8.  [`formDisabledCallback`][37]\n9.  [`formResetCallback`][38]\n10. [`formStateRestoreCallback`][39]\n\n## Scaffolds\n\n1.  [Basic][22]\n2.  [DashBoard][40]\n3.  [Mobile][41]\n4.  [Static site][42]\n\n## Ecosystem\n\nWe recommend these libraries to use with WebCell:\n\n-   **State management**: [MobX][3] (also powered by **TypeScript** \u0026 **Decorator**)\n-   **Router**: [Cell Router][43]\n-   **UI components**\n\n    -   [BootCell][44] (based on **BootStrap v5**)\n    -   [MDUI][45] (based on **Material Design v3**)\n    -   [GitHub Web Widget][46]\n\n-   **HTTP request**: [KoAJAX][47] (based on **Koa**-like middlewares)\n-   **Utility**: [Web utility][48] methods \u0026 types\n-   **Event stream**: [Iterable Observer][49] (`Observable` proposal)\n-   **MarkDown integration**: [Parcel MDX transformer][50] (**MDX** Compiler plugin)\n\n## Roadmap\n\n-   [x] [Server-side Render][51]\n-   [x] [Async Component loading][52]\n\n## [v2 to v3 migration](./guide/Migrating.md)\n\n## More guides\n\n1.  [Development contribution](./guide/Contributing.md)\n\n[1]: https://www.webcomponents.org/\n[2]: https://facebook.github.io/jsx/\n[3]: https://mobx.js.org/\n[4]: https://www.typescriptlang.org/\n[5]: https://libraries.io/npm/web-cell\n[6]: https://github.com/EasyWebApp/WebCell/actions/workflows/main.yml\n[7]: https://github.com/996icu/996.ICU/blob/master/LICENSE\n[8]: https://github.com/jaywcjlove/awesome-uikit\n[9]: https://tech-query.me/programming/web-components-practise/slide.html\n[10]: https://gitter.im/EasyWebApp/community?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\n[11]: https://codesandbox.io/p/devbox/9gyll?embed=1\u0026file=%2Fsrc%2FClock.tsx\n[12]: https://nodei.co/npm/web-cell/\n[13]: https://www.typescriptlang.org/\n[14]: https://github.com/tc39/proposal-decorators\n[15]: https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html\n[16]: https://www.webcomponents.org/\n[17]: https://github.com/EasyWebApp/DOM-Renderer\n[18]: https://mobx.js.org/observable-state.html#observable\n[19]: https://mobx.js.org/enabling-decorators.html\n[20]: https://facebook.github.io/jsx/\n[21]: https://parceljs.org/\n[22]: https://github.com/EasyWebApp/WebCell-scaffold\n[23]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components\n[24]: https://web.dev/articles/custom-elements-v1\n[25]: https://web.dev/articles/shadowdom-v1\n[26]: https://web.dev/articles/more-capable-form-controls\n[27]: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties\n[28]: https://developer.chrome.com/docs/web-platform/view-transitions/\n[29]: https://rse.github.io/es6-features/\n[30]: https://web-cell.dev/web-utility/interfaces/CustomElement.html#connectedCallback\n[31]: https://web-cell.dev/web-utility/interfaces/CustomElement.html#disconnectedCallback\n[32]: https://web-cell.dev/web-utility/interfaces/CustomElement.html#attributeChangedCallback\n[33]: https://web-cell.dev/web-utility/interfaces/CustomElement.html#adoptedCallback\n[34]: https://web-cell.dev/WebCell/interfaces/WebCell.html#updatedCallback\n[35]: https://web-cell.dev/WebCell/interfaces/WebCell.html#mountedCallback\n[36]: https://web-cell.dev/web-utility/interfaces/CustomFormElement.html#formAssociatedCallback\n[37]: https://web-cell.dev/web-utility/interfaces/CustomFormElement.html#formDisabledCallback\n[38]: https://web-cell.dev/web-utility/interfaces/CustomFormElement.html#formResetCallback\n[39]: https://web-cell.dev/web-utility/interfaces/CustomFormElement.html#formStateRestoreCallback\n[40]: https://github.com/EasyWebApp/WebCell-dashboard\n[41]: https://github.com/EasyWebApp/WebCell-mobile\n[42]: https://github.com/EasyWebApp/mark-wiki\n[43]: https://web-cell.dev/cell-router/\n[44]: https://bootstrap.web-cell.dev/\n[45]: https://www.mdui.org/\n[46]: https://tech-query.me/GitHub-Web-Widget/\n[47]: https://web-cell.dev/KoAJAX/\n[48]: https://web-cell.dev/web-utility/\n[49]: https://web-cell.dev/iterable-observer/\n[50]: https://github.com/EasyWebApp/Parcel-transformer-MDX\n[51]: https://developer.chrome.com/docs/css-ui/declarative-shadow-dom\n[52]: https://legacy.reactjs.org/docs/react-api.html#reactlazy\n","funding_links":["https://paypal.me/TechQuery","https://tech-query.me/image/TechQuery-Alipay.jpg"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feasywebapp%2Fwebcell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feasywebapp%2Fwebcell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feasywebapp%2Fwebcell/lists"}