{"id":20117812,"url":"https://github.com/qrailibs/bubblejs","last_synced_at":"2025-07-18T09:39:02.434Z","repository":{"id":170309152,"uuid":"555765356","full_name":"qrailibs/BubbleJS","owner":"qrailibs","description":"✨ Frontend framework that embeds everywhere \u0026 fully type-safe.","archived":false,"fork":false,"pushed_at":"2023-07-02T13:41:36.000Z","size":9118,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-27T19:04:53.237Z","etag":null,"topics":["framework","mvc","mvvm","typescript","webcomponent"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qrailibs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2022-10-22T09:19:35.000Z","updated_at":"2023-09-27T19:47:46.000Z","dependencies_parsed_at":"2023-07-10T22:30:43.754Z","dependency_job_id":null,"html_url":"https://github.com/qrailibs/BubbleJS","commit_stats":null,"previous_names":["qrai/bubblejs","qrailibs/bubblejs"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qrailibs%2FBubbleJS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qrailibs%2FBubbleJS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qrailibs%2FBubbleJS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qrailibs%2FBubbleJS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qrailibs","download_url":"https://codeload.github.com/qrailibs/BubbleJS/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241564434,"owners_count":19982958,"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":["framework","mvc","mvvm","typescript","webcomponent"],"created_at":"2024-11-13T19:07:59.500Z","updated_at":"2025-03-02T19:41:23.750Z","avatar_url":"https://github.com/qrailibs.png","language":"TypeScript","readme":"# BubbleJS\nFrontend framework based on WebComponents with full support of TypeScript.\n\n# Features\n- [Components](#components)\n  - [`Refs`](#refs)\n  - [`WatchRefs`](#watchrefs)\n  - [`MapRefs`](#maprefs)\n  - [`CachedRefs`](#cachedrefs)\n  - [`Props`](#props)\n  - [`Elements`](#elements)\n  - [`Events`](#events)\n  - [`Slots`](#slots)\n  - [`Binding`](#binding)\n  - [Conditional elements (`--if`, `--else`)](#conditional-elements)\n  - [Conditional attributes (`attr:if`)](#conditional-attributes)\n  - [Loops](#loops)\n- [Routing \u0026 Views](#routing)\n- [Directives](#directives)\n- [Stores](#stores)\n- [Caching](#caching)\n\n## Components\nComponents are classes that represents WebComponents. To define your component use a `Component` decorator with tag name of component as argument:\n\n```ts\nimport { ComponentBase, Component, Ref, html } from 'bubblejs-core';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Ref()\n  public count: number = 0;\n\n  template() {\n    return html`\n      \u003cbutton @click=\"${this.onClick}\"\u003e\n        Clicked ${this.count} times\n      \u003c/button\u003e\n    `;\n  }\n\n  onClick() {\n    this.count++;\n  }\n\n  // Lifecycle hooks\n  onBeforeMount() {\n    console.log('Before mounted');\n  }\n  onMount() {\n    console.log('Mounted');\n  }\n  onAdopt() {\n    console.log('Adopted');\n  }\n  onDestroy() {\n    console.log('Destroyed');\n  }\n\n  styles() {\n    return /*css*/`\n      button {\n        padding: 12px 20px;\n\n        background: #4938ff;\n        color: white;\n\n        border: none;\n        border-radius: 8px;\n        outline: none;\n\n        cursor: pointer;\n\n        font-size: 16px;\n\n        transition: 0.3s ease-in-out;\n      }\n      button:hover {\n        background: #6456fb;\n      }\n    `;\n  }\n}\n```\n```html\n\u003cmy-component\u003e\u003c/my-component\u003e\n```\n\nWhen you define a component class, you should define a `template()` method, that returns HTML template that will be rendered.\nThe `styles()` method is optional and used to define CSS of your component.\n\n## Refs\nRefs are simply reactive data variables, that can be used inside component. To define a ref use a `Ref` decorator:\n\n```ts\nimport { ComponentBase, Component, Ref, html } from 'bubblejs-core';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Ref()\n  public count: number = 0;\n\n  template() {\n    return html`\n      \u003cspan\u003e\n        Count is ${ this.count }\n      \u003c/span\u003e\n    `;\n  }\n\n  onMount() {\n    setInterval(() =\u003e {\n      this.count++\n    }, 100);\n  }\n}\n```\n```html\n\u003cmy-component\u003e\u003c/my-component\u003e\n```\n\nRefs can be accessed via `this` context inside `template`, lifecycle hooks, event listeners, methods.\n\nFor more complex scenarios `Ref` has callbacks that you can use:\n- `init` (Called before mount, when value of ref should be initialized)\n- `get` (Called when someone is trying to get ref value)\n- `set` (Called when someone is trying to change ref value)\n```ts\nimport { ComponentBase, Component, Ref, html } from 'bubblejs-core';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Ref({\n    init(key) =\u003e console.log('Initialized ref'),\n    get(key) =\u003e console.log('Trying to get ref value'),\n    set(key, newVal, oldVal) =\u003e console.log('Trying to change ref value'),\n  })\n  public count: number = 0;\n\n  template() {\n    return html`\n      \u003cspan\u003e\n        Count is ${ this.count }\n      \u003c/span\u003e\n    `;\n  }\n\n  onMount() {\n    setInterval(() =\u003e {\n      this.count++\n    }, 100);\n  }\n}\n```\n\n## WatchRefs\n...\n\n## Props\nProps allows to define acceptable properties of your component. To define a component prop use a `Prop` decorator:\n\n```ts\nimport { ComponentBase, Component, Prop, html } from 'bubblejs-core';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Prop()\n  public text: string = '';\n\n  template() {\n    return html`\n      \u003cspan\u003e${ this.text }\u003c/span\u003e\n    `;\n  }\n}\n```\n```html\n\u003cmy-component text=\"Hello world\"\u003e\u003c/my-component\u003e\n```\n\n**Notice**: you can pass arrays/objects/functions as prop value also, but only inside another component template.\n\nProps can be accessed via `this` context inside `template`, lifecycle hooks, event listeners, methods.\n\n## Elements\nElements are simply refs that stores html elements. You can use them to automatically get html element from your template and work with it.\n```ts\nimport { ComponentBase, Component, Element, html } from 'bubblejs-core';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Element('.some-input')\n  public inputEl?: HTMLInputElement;\n\n  template() {\n    return html`\n      \u003cdiv class=\"some-container\"\u003e\n        \u003cinput class=\"some-input\"\u003e\n      \u003c/div\u003e\n    `;\n  }\n\n  onMount() {\n    if(this.inputEl) {\n      this.inputEl.value = 'hello world!';\n    }\n  }\n}\n```\n## Events\nEvents allows you to emit custom events up to parent component.\nYou just have to call `this.emit(eventName, ...args)` method to emit your event.\n```ts\nimport { ComponentBase, Component, html } from 'bubblejs-core';\n\n@Component('my-child')\nclass MyChild extends ComponentBase {\n  template() {\n    return html`\n      \u003cbutton @click=\"${this.onClick}\"\u003e\n        Button!\n      \u003c/button\u003e\n    `;\n  }\n\n  onClick(e: Event) {\n    e.preventDefault();\n\n    // Emit custom event\n    this.emit('some-event');\n  }\n}\n\n@Component('my-parent')\nclass MyParent extends ComponentBase {\n  template() {\n    return html`\n      \u003cmychild @some-event=\"${() =\u003e console.log('fired!')}\"\u003e\u003c/mychild\u003e\n    `;\n  }\n}\n```\n\n**Notice**: `emit` method is calls native `dispatchEvent`, that means you can also\nuse native `addEventListener` to catch your component's custom event.\n\n## Slots\nin progress.\n\n## Binding\nin progress.\n\n## Conditional elements\nFor conditional rendering framework is provides special reserved directives `--if` and `--else`. You should pass boolean value, if value will be `true`, the element will be rendered, otherwise it will render the `--else` element.\n```ts\nimport { ComponentBase, Component, Ref, html } from './lib/index';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Ref()\n  public doRender: boolean = false;\n\n  template() {\n    return html`\n      \u003cp --if=\"${this.doRender}\"\u003e\n        Hello world!\n      \u003c/p\u003e\n      \u003cp --else\u003e\n        Bye world!\n      \u003c/p\u003e\n    `;\n  }\n}\n```\nAfter render:\n```html\n\u003cmy-component\u003e\n  \u003cp\u003eBye world!\u003c/p\u003e\n\u003c/my-component\u003e\n```\n\n## Conditional attributes\nAs you apply conditions to render or not elements, you can apply conditions to set attribute or not. Framework provides special syntax for conditional attributes: `attrName:if=\"${true | false}\"`. If value will be true, the element will have `attrName=\"\"` attribute, otherwise attribute will be not added.\n\n```ts\nimport { ComponentBase, Component, Ref, html } from './lib/index';\n\n@Component('my-component')\nclass MyComponent extends ComponentBase {\n  @Ref()\n  public isRequired: boolean = false;\n\n  @Ref()\n  public isDisabled: boolean = true;\n\n  template() {\n    return html`\n      \u003cinput required:if=\"${this.isRequired}\" disabled:if=\"${this.isDisabled}\"\u003e\n    `;\n  }\n}\n```\nAfter render:\n```html\n\u003cmy-component\u003e\n  \u003cinput disabled=\"\"\u003e\n\u003c/my-component\u003e\n```\n\n## Loops\nLoops can be helpful when you need to render a list of something. To create a loop you can use a native array `.map` method.\n```ts\n\nimport { ComponentBase, Component, Ref, html } from 'bubblejs-core';\n\n@Component('some-list')\nclass SomeList extends ComponentBase {\n  @Ref()\n  public items: string[] = [\n    'hello',\n    'world',\n    'guys'\n  ];\n\n  template() {\n    return html`\n      \u003cdiv class=\"list\"\u003e\n        ${this.items.map((item) =\u003e html`\n          \u003cdiv class=\"list__item\"\u003e\n            \u003cp\u003e${item}\u003c/p\u003e\n          \u003c/div\u003e\n        `)}\n      \u003c/div\u003e\n    `;\n  }\n}\n```\n\n**Notice**: you can use alternative ways to do a loops, but in any way the returned value should be of type `HtmlTemplate[]`.\n\n## Routing\nFramework also provides a built-in lightweight routing system.\nFirstly you have to use `bubble-router` component which is renders\ncurrent view inside:\n```html\n\u003cbubble-router\u003e\u003c/bubble-router\u003e\n```\n\nAfter that you can define views in your code using `View` decorator:\n```ts\nimport { ComponentBase, View, Ref, html } from 'bubblejs-core';\n\n@View('/')\nclass IndexView extends ComponentBase {\n  @Ref()\n  public subject = 'world';\n\n  template() {\n    return html`\n      \u003cp\u003eHello ${subject}!\u003c/p\u003e\n    `;\n  }\n}\n```\nAs you see, views are basically components but defined in different way.\n\n## Directives\n-\n\n## Stores\n-\n\n## Caching\n-","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqrailibs%2Fbubblejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqrailibs%2Fbubblejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqrailibs%2Fbubblejs/lists"}