{"id":18316929,"url":"https://github.com/calebowens/web-element-composition-utility","last_synced_at":"2026-04-02T02:40:43.758Z","repository":{"id":44623148,"uuid":"421195054","full_name":"calebowens/web-element-composition-utility","owner":"calebowens","description":"A library for building composable, reactive web components.","archived":false,"fork":false,"pushed_at":"2024-01-20T19:27:10.000Z","size":311,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-09T19:05:09.406Z","etag":null,"topics":["web","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/calebowens.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2021-10-25T21:51:34.000Z","updated_at":"2022-04-23T22:31:31.000Z","dependencies_parsed_at":"2025-04-05T21:42:17.373Z","dependency_job_id":null,"html_url":"https://github.com/calebowens/web-element-composition-utility","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/calebowens/web-element-composition-utility","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebowens%2Fweb-element-composition-utility","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebowens%2Fweb-element-composition-utility/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebowens%2Fweb-element-composition-utility/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebowens%2Fweb-element-composition-utility/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calebowens","download_url":"https://codeload.github.com/calebowens/web-element-composition-utility/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebowens%2Fweb-element-composition-utility/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271588743,"owners_count":24785751,"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","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["web","webcomponents"],"created_at":"2024-11-05T18:04:17.678Z","updated_at":"2026-04-02T02:40:43.733Z","avatar_url":"https://github.com/calebowens.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web Element Composition Utility\n\nDue to limitations in typescripts .d.ts type declarations, its not possible to mark properties as private or protected. To try and help you understand what is intended for the user to call, I have prefixed \"unsafe\" methods with an underscore.\n\n## API Documentation\n\nTo view API docs visit [https://calebowens.github.io/web-element-composition-utility/](https://calebowens.github.io/web-element-composition-utility/)\n\n## About\n\nWECU is a library that lets you create web components and compose them together using an intuitive OO system.\n\nThe idea behind WECU is that it lets the user take control of the program, with the user calling it on their own terms. WECU is based arround the idea that the GUI is bound to your data in the class, just how your actions are also. It results in creating a nice abstraction over the DOM for implementing reactive behaviour without taking any control away from the programmer like a framework would.\n\n## Who This is For\n\nThis library will be ideal for:\n\n- People who want to be able to manage each part of their web app\n- Still want convenience of being able to create and compose components\n- Would like to be able to program in a more traditional OOP way\n\n## Demo Projects\n\nI've written a demo project using webpack and typescript that I encourage you to check out at [https://github.com/calebowens/wecu-example](https://github.com/calebowens/wecu-example)\n\n## Using Deno as Bundler\n\nFor usage with deno you can use the import link `https://deno.land/x/wecu@\u003cVERSION_NUMBER\u003e/deno_dist/index.ts`.\n\nFor bundling deno you'll need to use the enable libs \"dom\" and \"es2015\" in your deno.json.\n\n## Examples\n\nThese examples have been written with the following typescript setup in mind:\n\n- Run `yarn add typescript vite`\n- Add `tsconfig.json`\n\n```json\n{\n    \"compilerOptions\": {\n        \"target\": \"es2015\",\n        \"experimentalDecorators\": true,\n        \"moduleResolution\": \"node\",\n        \"outDir\": \"./lib\",\n        \"esModuleInterop\": true,\n        \"declaration\": true,\n        \"strict\": true\n    },\n    \"include\": [\n        \"./src\"\n    ]\n}\n```\n\n- Run `yarn vite`\n\n\n### Root of an app\n\nmain.ts\n\n```ts\nimport { Component, P, mountComponent } from \"wecu\";\n\nclass Root extends Component {\n  render() {\n    return [new P(\"Hello World!\")];\n  }\n}\n\nmountComponent(Root, '#app')\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Conditional Rendering\n\nmain.ts\n\n```ts\nimport { Component, P, Button, mountComponent } from \"wecu\";\n\nclass Root extends Component {\n  private toggle = new Button(\"Toggle\");\n  private show = true;\n\n  constructor() {\n    super();\n\n    this.toggle.element.addEventListener(\"click\", () =\u003e {\n      this.show = !this.show; // We've modified state so we need to re-render the button\n      this.rerender();\n    });\n  }\n  render() {\n    if (this.show) {\n      return [new P(\"HelloThere!\"), this.toggle];\n    } else {\n      return [this.toggle];\n    }\n  }\n}\n\nmountComponent(Root, '#app')\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Using an observable variable\n\nmain.ts\n\n```ts\nimport { Component, Observable, P, Button, mountComponent } from \"wecu\";\n\nclass Root extends Component {\n  private toggle = new Button(\"Toggle\");\n\n  private show = new Observable(true);\n\n  constructor() {\n    super();\n\n    // Register the event listener on the internal element of the button\n    this.toggle.element.addEventListener(\"click\", () =\u003e {\n      this.show.value = !this.show.value;\n    });\n\n    // Rather than calling rerender in the button event listener, I can\n    //   observe the value for changes\n    this.show.onUpdate(() =\u003e {\n      this.rerender();\n    });\n  }\n\n  render() {\n    if (this.show) {\n      return [new P(\"Hello There!\"), this.toggle];\n    } else {\n      return [this.toggle];\n    }\n  }\n}\n\nmountComponent(Root, '#app')\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Registering a Component as a Web Component\n\nmain.ts\n\n```ts\nimport { Component, registerComponent, P } from \"wecu\";\n\nclass Root extends Component {\n  render() {\n    return [new P(\"Hello World!\")];\n  }\n}\n\nregisterComponent(Root, \"x-root\");\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cx-root\u003e\u003c/x-root\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Styling Components\nmain.ts\n\n```ts\nimport { Component, registerComponent, P, Style } from \"wecu\";\n\nclass Root extends Component {\n  render() {\n    return [\n      new Style(`\n      :host {\n        width: 100%;\n      }\n\n      p {\n        color: green;\n      }\n    `),\n      new P(\"Hello World!\")\n    ];\n  }\n}\n\nregisterComponent(Root, \"x-root\");\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cx-root\u003e\u003c/x-root\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Using an Emitter\n\nmain.ts\n\n```ts\nimport { Button, Component, Input, Emitter, P, registerComponent } from \"wecu\";\n\nexport default class CreateTask extends Component {\n  private addTask = new Button(\"Create Task\");\n  private input = new Input();\n\n  public taskEmitter = new Emitter\u003cstring\u003e();\n\n  constructor() {\n    super();\n\n    this.addTask.element.addEventListener(\"click\", () =\u003e {\n      this.taskEmitter.emit(this.input.element.value);\n\n      this.input.element.value = \"\";\n    });\n  }\n\n  render() {\n    return [this.input, this.addTask];\n  }\n}\n\nexport default class Root extends Component {\n  private tasks: P[] = [];\n  private taskCreator = new CreateTask();\n\n  constructor() {\n    super();\n\n    this.taskCreator.taskEmitter.onEmit((title) =\u003e {\n      this.tasks.push(new P(title));\n\n      this.rerender();\n    });\n  }\n\n  render() {\n    return [this.taskCreator, ...this.tasks];\n  }\n}\n\nregisterComponent(Root, \"x-root\");\n```\n\nindex.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"main.js\" async defer\u003e\u003c/script\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cx-root\u003e\u003c/x-root\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebowens%2Fweb-element-composition-utility","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalebowens%2Fweb-element-composition-utility","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebowens%2Fweb-element-composition-utility/lists"}