{"id":14990092,"url":"https://github.com/adebola-io/bullet","last_synced_at":"2026-02-25T20:30:15.482Z","repository":{"id":245347545,"uuid":"817977059","full_name":"adebola-io/bullet","owner":"adebola-io","description":"JSX web components. 🦫","archived":true,"fork":false,"pushed_at":"2024-09-16T06:19:50.000Z","size":687,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-25T08:27:24.206Z","etag":null,"topics":["custom-elements","framework","javascript","web-components"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@adbl/bullet","language":"JavaScript","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/adebola-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2024-06-20T21:06:15.000Z","updated_at":"2024-11-14T15:49:33.000Z","dependencies_parsed_at":"2024-06-21T16:25:15.664Z","dependency_job_id":"b61f63ad-a244-4625-be8e-dcc3b1dc1973","html_url":"https://github.com/adebola-io/bullet","commit_stats":{"total_commits":81,"total_committers":1,"mean_commits":81.0,"dds":0.0,"last_synced_commit":"08d9f261c73394c765b2f5cebe24b79ed80d345a"},"previous_names":["adebola-io/bullet"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adebola-io%2Fbullet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adebola-io%2Fbullet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adebola-io%2Fbullet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adebola-io%2Fbullet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adebola-io","download_url":"https://codeload.github.com/adebola-io/bullet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239862749,"owners_count":19709461,"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":["custom-elements","framework","javascript","web-components"],"created_at":"2024-09-24T14:19:26.839Z","updated_at":"2026-02-25T20:30:15.425Z","avatar_url":"https://github.com/adebola-io.png","language":"JavaScript","readme":"# bullet\n\nA lightweight, efficient, and flexible library for Web Components.\n\n[![downloads (@adbl/bullet)](https://img.shields.io/npm/dm/@adbl/bullet?label=downloads)](https://www.npmjs.com/package/@adbl/bullet)\n\nBullet is a robust and performant solution for creating reusable, encapsulated HTML elements. It offers a streamlined API for developing complex web applications with ease and efficiency.\n\n\u003c!-- TOC --\u003e\n\n- [bullet](#bullet)\n  - [Key Features](#key-features)\n  - [Installation](#installation)\n  - [Usage](#usage)\n    - [Quick Start](#quick-start)\n    - [Custom Elements](#custom-elements)\n    - [The `createElement` Function](#the-createelement-function)\n    - [JSX Syntax](#jsx-syntax)\n    - [Usage without JSX](#usage-without-jsx)\n  - [Styling](#styling)\n  - [Importing External Stylesheets](#importing-external-stylesheets)\n  - [Attributes](#attributes)\n  - [Anonymous components](#anonymous-components)\n  - [Event Handling](#event-handling)\n  - [Rendering Lists](#rendering-lists)\n    - [Conditional Rendering](#conditional-rendering)\n  - [Lifecycle Methods](#lifecycle-methods)\n    - [`connected`](#connected)\n    - [`disconnected`](#disconnected)\n  - [Async components](#async-components)\n    - [Loading and Error Handling](#loading-and-error-handling)\n  - [Routing](#routing)\n    - [Setting Up the Router](#setting-up-the-router)\n    - [Route Configuration](#route-configuration)\n    - [Implementing the Router](#implementing-the-router)\n    - [Nested Routing with Inner Outlets](#nested-routing-with-inner-outlets)\n    - [Lazy Loading Routes](#lazy-loading-routes)\n    - [Programmatic Navigation](#programmatic-navigation)\n    - [Dynamic Route Parameters](#dynamic-route-parameters)\n    - [Wildcard Routes](#wildcard-routes)\n  - [Why bullet?](#why-bullet)\n  - [License](#license)\n\n\u003c!-- /TOC --\u003e\n\u003c!-- /TOC --\u003e\n\n## Key Features\n\n- **Lightweight**: Minimal overhead for optimal performance\n- **Web Components**: First-class support for custom elements\n- **JSX Support**: Familiar syntax for React developers\n- **Reactive**: Built-in reactivity with `@adbl/cells`\n- **Async Components**: Easy handling of asynchronous rendering\n- **Routing**: Built-in routing system for single-page applications\n- **Scoped Styling**: Encapsulated CSS using Shadow DOM\n- **No Build Step Required**: Can be used directly in the browser\n\n## Installation\n\nTo create a new Bullet project, run the following command:\n\n```bash\nnpx create-bullet-app\n```\n\n- Configure project when prompted.\n\n- Navigate to project directory:\n\n  ```bash\n  cd your-project-name\n  ```\n\n- Install dependencies:\n\n  ```bash\n  npm install\n  ```\n\n- Start development server:\n\n  ```bash\n  npm run dev\n  ```\n\n- Open `http://localhost:5173` in your browser.\n\nYou're now ready to build with Bullet! 🚀\n\n## Usage\n\n### Quick Start\n\nHere's a simple example to get you started with Bullet:\n\n```jsx\nimport { createElement } from '@adbl/bullet';\nimport { Cell } from '@adbl/cells';\n\n// Create a simple counter component\nconst Counter = createElement({\n  tag: 'my-counter',\n  render: () =\u003e {\n    const count = Cell.source(0);\n\n    return (\n      \u003cdiv\u003e\n        \u003coutput\u003e{count}\u003c/output\u003e\n        \u003cbutton onClick={() =\u003e count.value++}\u003eIncrement\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  },\n});\n\n// Use the component\ndocument.body.append(\u003cCounter /\u003e);\n```\n\nThis example creates a simple counter component with Bullet and adds it to the page. The following sections will dive deeper into Bullet's features and usage.\n\n### Custom Elements\n\nCustom elements in Bullet allow you to define new HTML tags with associated behavior. They encapsulate functionality and can be reused throughout your application.\n\n### The `createElement` Function\n\nThe `createElement` function is the core of Bullet's component creation system. It takes an object as an argument, which defines the properties and behavior of your custom element.\n\nThe key properties of the `createElement` object are:\n\n1. `tag`: A string that defines the name of a custom element.\n2. `render`: A function that returns the content of a custom element.\n\nHere's an example to illustrate these concepts:\n\n```jsx\nimport { createElement } from '@adbl/bullet';\n\nconst Greeting = createElement({\n  tag: 'app-greeting',\n  render: () =\u003e {\n    return \u003ch1\u003eHello, World!\u003c/h1\u003e;\n  },\n});\n// Use it like this:\ndocument.body.append(\u003cGreeting /\u003e);\n```\n\nThis creates a custom `\u003capp-greeting\u003e` element that displays \"Hello, World!\". The element can also be instantiated in HTML:\n\n```html\n\u003capp-greeting\u003e\u003c/app-greeting\u003e\n```\n\n---\n\n### JSX Syntax\n\nJSX is a syntax extension for JavaScript that allows you to write HTML-like code within your JavaScript files. It is most famously used with React to describe the structure and appearance of UI components.\n\nBullet supports a similar syntax, and compiles to regular DOM nodes.\n\n```jsx\nimport { createElement } from '@adbl/bullet';\n\nconst Greeting = createElement({\n  tag: 'app-greeting',\n  render: (props) =\u003e {\n    return (\n      \u003cdiv\u003e\n        \u003ch1\u003eHello, {props.name}!\u003c/h1\u003e\n        \u003cp\u003eWelcome to Bullet\u003c/p\u003e\n      \u003c/div\u003e\n    );\n  },\n});\n\n// Usage\ndocument.body.append(\u003cGreeting name=\"World\" /\u003e);\n```\n\nJSX is transpiled to regular JavaScript function calls and objects before being executed in the browser.\n\n### Usage without JSX\n\nSince the JSX is syntactic sugar for regular JavaScript, you can use Bullet without it.\n\n```js\nimport { createElement } from '@adbl/bullet';\n\nconst Greeting = createElement({\n  tag: 'app-greeting',\n  render: (props) =\u003e {\n    const greetingElement = document.createElement('div');\n    const headingElement = document.createElement('h1');\n    const paragraphElement = document.createElement('p');\n\n    headingElement.innerHTML = `Hello, ${props.name}!`;\n    paragraphElement.innerHTML = 'Welcome to Bullet';\n\n    greetingElement.appendChild(headingElement);\n    greetingElement.appendChild(paragraphElement);\n    return greetingElement;\n  },\n});\n```\n\nYou can also use the html template function to automatically parse strings:\n\n```jsx\nimport { component, html } from '@adbl/bullet';\n\nconst Card = createElement({\n  tag: 'product-card',\n  render: (props) =\u003e {\n    return html`\n      \u003cdiv class=\"card\"\u003e\n        \u003ch1\u003e${props.name}\u003c/h1\u003e\n        \u003cimg src=${props.imgSrc} alt=${props.imgAlt} /\u003e\n      \u003c/div\u003e\n    `;\n  },\n});\n```\n\nIt is mostly recommended to use Bullet with JSX, as it provides a lot more features and functionality.\n\n---\n\n## Styling\n\nComponents can be styled using the `styles` property on the component object, and the `css` function, which can be used to generate CSS from JavaScript strings.\n\n```tsx\nimport { createElement, css } from '@adbl/bullet';\n\nconst AppButton = createElement({\n  tag: 'app-button',\n  render: (props) =\u003e {\n    return \u003cbutton\u003e{props.label}\u003c/button\u003e;\n  },\n\n  styles: css`\n    button {\n      background-color: red;\n      color: white;\n      border-radius: 1rem;\n      padding: 4rem 8rem;\n    }\n  `,\n});\n```\n\nBy default, the stylesheets are scoped using the shadow DOM. This means in the example above, the styles for `button` will affect any `\u003cbutton\u003e` element _within_ the `AppButton` component, and will be unreachable from outside it. This provides native encapsulation and isolates styles, preventing side effects on other components.\n\nWhen the html is rendered:\n\n```html\n\u003capp-button\u003e\n  \u003c!-- # In the shadow DOM: --\u003e\n  \u003c!-- This button will have a red background, rounded corners, and padding. --\u003e\n  \u003cbutton\u003eClick me\u003c/button\u003e\n\u003c/app-button\u003e\n\u003c!-- But this button will not. --\u003e\n\u003cbutton\u003eClick me too\u003c/button\u003e\n```\n\nThere may be a need to instead apply styles to the whole document, and this can be achieved by using the `globalStyles` property instead:\n\n```tsx\nimport { createElement, css } from '@adbl/bullet';\n\nconst AppButton = createElement({\n  tag: 'app-button',\n  render: (props) =\u003e {\n    return \u003cbutton\u003e{props.label}\u003c/button\u003e;\n  },\n\n  styles: css`\n    button {\n      background-color: red;\n      color: white;\n      border-radius: 1rem;\n      padding: 4rem 8rem;\n    }\n  `,\n\n  globalStyles: css`\n    body {\n      font-family: sans-serif;\n      background-color: black;\n    }\n  `,\n});\n```\n\nWhenever there is at least one instance of the component in the DOM, the document body will apply the given styles.\n\nNOTE: This still wont affect the styles of other custom elements, only styles of elements in the root DOM. The best use case for this is to apply styles to the body or html element when a custom element is present in the DOM.\n\n## Importing External Stylesheets\n\nBullet also supports importing external CSS files directly into your components. This can be particularly useful for organizing your styles or when working with existing CSS files. To import an external stylesheet when using Vite, you can use the `?inline` query parameter in your import statement.\n\nHere's an example of how to import and use an external CSS file in your Bullet component:\n\n```css\n/* my-component/styles.css */\n\n:host {\n  /* Styles for the custom element itself. */\n  width: 100px;\n  height: 100px;\n  background-color: red;\n}\n```\n\n```tsx\n/* my-component/index.tsx */\n\nimport { createElement, css } from '@adbl/bullet';\nimport styles from './styles.css?inline';\n\nconst MyComponent = createElement({\n  tag: 'my-component',\n  styles: css(styles),\n  render: () =\u003e {\n    return 'Hello, World!';\n  },\n});\n```\n\nThis way you can separate your styles from your components and keep them organized.\n\n## Attributes\n\nThere are instances where you want to pass attributes to your custom elements. Bullet supports this with the `attr:` prefix.\n\n```tsx\nimport { createElement } from '@adbl/bullet';\n\nconst AppInput = createElement({\n  tag: 'app-input',\n  render: (props) =\u003e {\n    return \u003cinput class={props.class} type=\"text\" /\u003e;\n  },\n});\n\nconst myInput = \u003cAppInput attr:class=\"custom-app-input\" class=\"inner-input\" /\u003e;\ndocument.body.appendChild(myInput);\n```\n\nWhen the element is rendered, the `attr:` prefix will be stripped from the attribute name, and the attribute will be set on the element, while the rest of the attributes will be passed as props.\n\n```html\n\u003capp-input class=\"custom-app-input\"\u003e\n  \u003cinput class=\"inner-input\" /\u003e\n\u003c/app-input\u003e\n```\n\n---\n\n## Anonymous components\n\nYou can also declare custom elements without tag names, meaning the tag names will be auto-generated:\n\n```jsx\nconst Heading = createElement((props) =\u003e {\n  return \u003ch1\u003e{props.text}\u003c/h1\u003e)\n};\n\ndocument.body.append(\u003cHeading text=\"Hello there\" /\u003e);\n```\n\n---\n\n## Event Handling\n\nIn Bullet, you can pass event handlers to inner elements using the regular JSX syntax.\n\n```tsx\nimport { createElement } from '@adbl/bullet';\n\nconst Button = createElement({\n  tag: 'my-button',\n  render: (props) =\u003e {\n    return \u003cbutton onClick={props.onClick}\u003e{props.label}\u003c/button\u003e;\n  },\n});\n\nconst handleClick = () =\u003e {\n  console.log('Button clicked!');\n};\n\n// Usage\n\u003cButton onClick={handleClick} label=\"Click me\" /\u003e;\n```\n\nIn this example, the handleClick function is passed as the onClick prop to the Button component. When the button is clicked, the handleClick function will be called, and \"Button clicked!\" will be logged to the console.\n\nAs discussed in the [Attributes](#attributes) section, you can also pass events to your custom elements using the `attr:` prefix.\n\n```tsx\nimport { createElement } from '@adbl/bullet';\n\nconst Confetti = createElement({\n  tag: 'party-confetti',\n  render: () =\u003e {\n    return '🎉';\n  },\n});\n\ndocument.body.append(\n  \u003cConfetti attr:onClick={() =\u003e alert('Hooray! Confetti explosion!')} /\u003e\n);\n```\n\nIn the above example, there is no inner element in `\u003cparty-confetti\u003e` element; the click handler is set on the element itself.\n\n---\n\n## Rendering Lists\n\nThere are a number of ways to render lists in bullet. A list can be rendered by mapping over an array of data, similar to how it would be done in a React component.\n\n```tsx\nimport { createElement } from '@adbl/bullet';\n\nconst taskItems = ['Learn Bullet', 'Build a web app', 'Deploy to production'];\n\nconst TodoList = createElement({\n  tag: 'todo-list',\n  render: () =\u003e {\n    return (\n      \u003cul\u003e\n        {taskItems.map((todo) =\u003e {\n          return \u003cli\u003e{todo}\u003c/li\u003e;\n        })}\n      \u003c/ul\u003e\n    );\n  },\n});\n```\n\nYou can also use the `For` function exported from the bullet library. A major advantage is that it allows you to map over other iterable objects, such as Maps and Sets.\n\n```tsx\nimport { createElement, For } from '@adbl/bullet';\n\nconst TodoList = createElement({\n  tag: 'todo-list',\n  render: () =\u003e {\n    return (\n      \u003cul\u003e\n        {For(taskItems, (item) =\u003e {\n          return \u003cli\u003e{item}\u003c/li\u003e;\n        })}\n      \u003c/ul\u003e\n    );\n  },\n});\n```\n\nBullet has first class support for the `@adbl/cells` library, which allows you to create reactive values and use them in Javascript.\n\nYou can create a reactive array cell, and whenever the array updates, the list of DOM\nelements it generates will be updated automatically.\n\n```tsx\nimport { createElement, For } from '@adbl/bullet';\nimport { Cell } from '@adbl/cells';\n\nconst listItems = Cell.source([\n  'Master the art of bullet-dodging',\n  'Create a time machine',\n  'Invent a new flavor of ice cream',\n]);\n\nconst TodoList = createElement({\n  tag: 'todo-list',\n  render: () =\u003e {\n    return (\n      \u003cul\u003e\n        {For(listItems, (item) =\u003e {\n          return \u003cli\u003e{item}\u003c/li\u003e;\n        })}\n      \u003c/ul\u003e\n    );\n  },\n});\ndocument.body.appendChild(\u003cTodoList /\u003e);\n\n// Later, when the listItems cell updates, the DOM will be updated automatically.\nlistItems.value.push('Befriend a unicorn');\n```\n\n\u003e The `For` function is aggressive when it comes to caching nodes for performance optimization.\n\u003e This means that the callback function provided to `For` **should** be pure and not rely on external state or produce side effects, because the callback function might not be called when you expect it to be.\n\u003e\n\u003e Here's an example to illustrate why this is important:\n\u003e\n\u003e ```tsx\n\u003e import { createElement, For } from '@adbl/bullet';\n\u003e import { Cell } from '@adbl/cells';\n\u003e\n\u003e let renderCount = 0;\n\u003e const items = Cell.source([\n\u003e   { id: 1, name: 'Alice' },\n\u003e   { id: 2, name: 'Bob' },\n\u003e   { id: 3, name: 'Charlie' },\n\u003e ]);\n\u003e\n\u003e const List = createElement({\n\u003e   tag: 'user-list',\n\u003e   render: () =\u003e {\n\u003e     return (\n\u003e       \u003cul\u003e\n\u003e         {For(items, (item) =\u003e {\n\u003e           renderCount++; // This is problematic!\n\u003e           return (\n\u003e             \u003cli\u003e\n\u003e               {item.name} (Renders: {renderCount})\n\u003e             \u003c/li\u003e\n\u003e           );\n\u003e         })}\n\u003e       \u003c/ul\u003e\n\u003e     );\n\u003e   },\n\u003e });\n\u003e\n\u003e document.body.append(\u003cList /\u003e);\n\u003e // Initial output:\n\u003e // - Alice (Renders: 1)\n\u003e // - Bob (Renders: 2)\n\u003e // - Charlie (Renders: 3)\n\u003e\n\u003e // Later:\n\u003e items.value.splice(1, 0, { id: 4, name: 'David' });\n\u003e // Actual output:\n\u003e // - Alice (Renders: 1)\n\u003e // - David (Renders: 4)\n\u003e // - Bob (Renders: 2)\n\u003e // - Charlie (Renders: 3)\n\u003e ```\n\u003e\n\u003e In this example, when we splice a new item into the middle of the array, the `For` function reuses the existing nodes for Alice, Bob, and Charlie. It only calls the callback function for the new item, David. This leads to an unexpected render count for David.\n\u003e\n\u003e To avoid this issue, use the reactive index provided by `For`:\n\u003e\n\u003e ```tsx\n\u003e const List = createElement({\n\u003e   tag: 'user-list',\n\u003e   render: () =\u003e {\n\u003e     return (\n\u003e       \u003cul\u003e\n\u003e         {For(items, (item, index) =\u003e {\n\u003e           return (\n\u003e             \u003cli\u003e\n\u003e               {item.name} (Index: {index})\n\u003e             \u003c/li\u003e\n\u003e           );\n\u003e         })}\n\u003e       \u003c/ul\u003e\n\u003e     );\n\u003e   },\n\u003e });\n\u003e ```\n\u003e\n\u003e This approach ensures correct behavior regardless of how the array is modified, as the index is always up-to-date.\n\n---\n\n### Conditional Rendering\n\nThe `If` function provides conditional rendering based on the truthiness of a value.\n\n```tsx\nimport { createElement, If } from '@adbl/bullet';\n\nconst isLoggedIn = false;\nconst username = 'John';\n\nconst ConditionalGreeting = createElement({\n  tag: 'conditional-greeting',\n  render: () =\u003e {\n    return (\n      \u003cdiv\u003e\n        {If(isLoggedIn, () =\u003e {\n          return \u003ch1\u003eWelcome back, {username}!\u003c/h1\u003e;\n        })}\n      \u003c/div\u003e\n    );\n  },\n});\n```\n\nThe `If` function also accepts a third argument, which is a callback function that is called when the condition is falsy.\n\n```tsx\nimport { createElement, If } from '@adbl/bullet';\n\nconst isLoggedIn = false;\nconst username = 'John';\n\nconst ConditionalGreeting = createElement({\n  tag: 'conditional-greeting',\n  render: () =\u003e (\n    \u003cdiv\u003e\n      {If(\n        isLoggedIn,\n        () =\u003e {\n          return \u003ch1\u003eWelcome back, {username}!\u003c/h1\u003e;\n        },\n        () =\u003e {\n          return \u003ch1\u003ePlease log in\u003c/h1\u003e;\n        }\n      )}\n    \u003c/div\u003e\n  ),\n});\n```\n\nIt can also be used with the `@adbl/cells` library. The `If` function will automatically update the DOM when the cell value changes.\n\n```tsx\nimport { createElement, If } from '@adbl/bullet';\nimport { Cell } from '@adbl/cells';\n\nconst isLoggedIn = Cell.source(false);\nconst username = Cell.source('');\n\nconst LoginStatus = createElement({\n  tag: 'login-status',\n  render: () =\u003e (\n    \u003cdiv\u003e\n      {If(\n        isLoggedIn,\n        () =\u003e (\n          \u003cdiv\u003e\n            \u003ch1\u003eWelcome back, {username}!\u003c/h1\u003e\n            \u003cbutton onClick={() =\u003e (isLoggedIn.value = false)}\u003eLogout\u003c/button\u003e\n          \u003c/div\u003e\n        ),\n        () =\u003e (\n          \u003cdiv\u003e\n            \u003ch1\u003ePlease log in\u003c/h1\u003e\n            \u003cinput\n              type=\"text\"\n              placeholder=\"Enter username\"\n              onInput={(e) =\u003e (username.value = e.target.value)}\n            /\u003e\n            \u003cbutton onClick={() =\u003e (isLoggedIn.value = true)}\u003eLogin\u003c/button\u003e\n          \u003c/div\u003e\n        )\n      )}\n    \u003c/div\u003e\n  ),\n});\n// Usage\ndocument.body.append(\u003cLoginStatus /\u003e);\n```\n\n## Lifecycle Methods\n\n### `connected`\n\nThe connected method is a lifecycle hook that is called when the component is mounted (inserted) into the DOM. This is a good place to perform side effects or initialize any resources that depend on the component being rendered.\n\nExample:\n\n```jsx\nconst MyComponent = createElement({\n  tag: 'my-component',\n  render: () =\u003e \u003cdiv\u003eHello, World!\u003c/div\u003e,\n  connected: (props) =\u003e {\n    console.log('Component mounted!');\n    console.log('Component Props: ', props);\n    // Perform side effects or initialize resources here\n  },\n});\n```\n\nIn this example, when an instance of `\u003cmy-component\u003e` is added to the DOM, the connected function will be called, and the message \"Component mounted!\" will be logged to the console, as well as props passed.\n\nThe connected method can optionally return a cleanup function, which will be called when the component is unmounted from the DOM.\n\n```jsx\nconst MyComponent = createElement({\n  tag: 'my-component',\n  render: () =\u003e \u003cdiv\u003eHello, World!\u003c/div\u003e,\n  connected: () =\u003e {\n    const interval = setInterval(() =\u003e {\n      console.log('Interval running...');\n    }, 1000);\n\n    return () =\u003e {\n      clearInterval(interval);\n      console.log('Interval cleared!');\n    };\n  },\n});\n```\n\n### `disconnected`\n\nThe `disconnected` method is a lifecycle hook that is called when the component is unmounted (removed) from the DOM. This is also a good place to perform cleanup tasks or release any resources that were initialized in the connected hook.\n\n```jsx\nconst MyComponent = createElement({\n  tag: 'my-component',\n  render: () =\u003e \u003cdiv\u003eHello, World!\u003c/div\u003e,\n  disconnected: () =\u003e {\n    console.log('Component unmounted!');\n    // Perform cleanup tasks or release resources here\n  },\n});\n```\n\nIn this example, when an instance of `\u003cmy-component\u003e` is removed from the DOM, the disconnected function will be called, and the message \"Component unmounted!\" will be logged to the console.\n\nThese lifecycle methods provide a way to hook into the component's lifecycle and perform actions at specific points, making it easier to manage side effects and resources within your components.\n\n---\n\n## Async components\n\nYou can define components that load asynchronously just by adding async before your render function.\n\n```jsx\nimport { createElement } from '@adbl/bullet';\n\nconst Products = createElement({\n  tag: `products`,\n\n  render: async () =\u003e {\n    const res = await fetch('https://dummyjson.com/products');\n    const data = await res.json();\n    const { products } = data;\n\n    return (\n      \u003cul\u003e\n        {data.map((item) =\u003e {\n          return \u003cli key={item.id}\u003e{item.name}\u003c/li\u003e;\n        })}\n      \u003c/ul\u003e\n    );\n  },\n});\n```\n\nYou can then use `Products` anywhere just like a regular component:\n\n```tsx\ndocument.body.append(\u003cProducts /\u003e);\n```\n\nThe component will be loaded on-demand when it is rendered.\n\n### Loading and Error Handling\n\nThe `initial` method is used to define a placeholder template that will be created when the component is first rendered, before the asynchronous render method completes.\n\nFor example:\n\n```tsx\nconst LoadingComponent = createElement({\n  tag: 'loading-component',\n  render: async () =\u003e {\n    await new Promise((resolve) =\u003e setTimeout(resolve, 2000));\n    return \u003cdiv\u003eContent loaded!\u003c/div\u003e;\n  },\n  initial: () =\u003e {\n    return \u003cdiv\u003eLoading...\u003c/div\u003e;\n  },\n});\n```\n\nThe `fallback` method is used to define a fallback template that will be rendered if the render method throws an error, or its Promise is rejected.\n\n```tsx\nconst ErrorComponent = createElement({\n  tag: 'error-component',\n  render: async () =\u003e {\n    throw new Error('Oops, something went wrong!');\n  },\n  fallback: (error) =\u003e {\n    return \u003cdiv\u003eError: {error.message}\u003c/div\u003e;\n  },\n});\n```\n\nIn both cases, the initial and fallback methods provide a way to handle the different states of an asynchronous component, ensuring a better user experience by displaying appropriate content or error messages.\n\n---\n\n## Routing\n\nBullet includes a straightforward yet powerful routing system for single-page applications. This built-in functionality integrates seamlessly with Bullet components, allowing for dynamic page navigation.\n\n### Setting Up the Router\n\nTo implement routing in your Bullet application, use the `createWebRouter` function:\n\n```tsx\nimport {\n  createWebRouter,\n  type RouteRecords,\n  createElement,\n} from '@adbl/bullet';\n\n// Define simple components\nconst Home = createElement({\n  tag: 'home-page',\n  render: () =\u003e {\n    return \u003ch1\u003eWelcome to the Home Page\u003c/h1\u003e;\n  },\n});\n\nconst About = createElement({\n  tag: 'about-page',\n  render: () =\u003e {\n    return \u003ch1\u003eAbout Us\u003c/h1\u003e;\n  },\n});\n\nconst NotFound = createElement({\n  tag: 'not-found',\n  render: () =\u003e {\n    return \u003ch1\u003e404 - Page Not Found\u003c/h1\u003e;\n  },\n});\n\n// Define routes\nconst routes: RouteRecords = [\n  {\n    name: 'home',\n    path: '/',\n    component: Home,\n  },\n  {\n    name: 'about',\n    path: '/about',\n    component: About,\n  },\n  {\n    name: 'not-found',\n    path: '*',\n    component: NotFound,\n  },\n];\n\n// Create and mount the router\nconst router = createWebRouter({ routes });\ndocument.body.appendChild(\u003crouter.Outlet /\u003e);\n```\n\n### Route Configuration\n\nThe `RouteRecords` type defines your application's route structure. Each route can include:\n\n- `name`: A unique route identifier\n- `path`: The URL path for the route\n- `component`: The component to render for this route\n- `children`: (Optional) Nested routes\n\n### Implementing the Router\n\nAfter creating a router, you can use it from within your elements with the `useRouter` hook. The router provides two essential custom elements:\n\n1. `Link`: For creating navigation links\n2. `Outlet`: Renders the current route's component\n\n```jsx\nimport { createElement, useRouter } from '@adbl/bullet';\n\nconst App = createElement({\n  tag: 'app-root',\n  render: () =\u003e {\n    const router = useRouter();\n    const { Link, Outlet } = router;\n\n    return (\n      \u003cdiv class=\"app\"\u003e\n        \u003cnav\u003e\n          \u003cLink to=\"/\"\u003eHome\u003c/Link\u003e\n          \u003cLink to=\"/about\"\u003eAbout\u003c/Link\u003e\n        \u003c/nav\u003e\n        \u003cmain\u003e\n          \u003cOutlet /\u003e\n        \u003c/main\u003e\n      \u003c/div\u003e\n    );\n  },\n});\n\nexport default App;\n```\n\n### Nested Routing with Inner Outlets\n\nBullet supports nested routing, allowing you to create more complex route structures with child routes. This is particularly useful for creating layouts with multiple levels of navigation or for organizing related routes.\n\nTo implement nested routing:\n\n1. Define child routes in your route configuration:\n\n```javascript\nconst routes: RouteRecords = [\n  {\n    name: 'dashboard',\n    path: '/dashboard',\n    component: Dashboard,\n    children: [\n      {\n        name: 'overview',\n        path: 'overview',\n        component: Overview,\n      },\n      {\n        name: 'stats',\n        path: 'stats',\n        component: Stats,\n      },\n    ],\n  },\n];\n```\n\nIn your parent component (e.g., Dashboard), use an inner Outlet to render child routes:\n\n```jsx\nconst Dashboard = createElement({\n  tag: 'dashboard-page',\n  render: () =\u003e {\n    const router = useRouter();\n    const { Link, Outlet } = router;\n\n    return (\n      \u003cdiv\u003e\n        \u003ch1\u003eDashboard\u003c/h1\u003e\n        \u003cnav\u003e\n          \u003cLink to=\"/dashboard/overview\"\u003eOverview\u003c/Link\u003e\n          \u003cLink to=\"/dashboard/stats\"\u003eStats\u003c/Link\u003e\n        \u003c/nav\u003e\n        \u003cOutlet /\u003e {/* This will render the active child route */}\n      \u003c/div\u003e\n    );\n  },\n});\n```\n\nWith this setup, when you navigate to `/dashboard/overview`, the Dashboard component will render, and its inner Outlet will display the Overview component. When you navigate to `/dashboard/stats`, the Dashboard component will still be rendered, but the inner Outlet will now display the Stats component.\n\n### Lazy Loading Routes\n\nBullet supports lazy loading of route components for improved performance:\n\n```javascript\nconst Settings = lazy(() =\u003e import('./Settings'));\n```\n\nThis technique allows for code splitting and on-demand loading of components.\n\n### Programmatic Navigation\n\nFor programmatic navigation, use the router's `navigate` method:\n\n```jsx\nconst ProfileButton = createElement({\n  render: () =\u003e {\n    const { navigate } = useRouter();\n    const goToProfile = () =\u003e navigate('/profile/123');\n\n    return \u003cbutton onClick={goToProfile}\u003eView Profile\u003c/button\u003e;\n  },\n});\n```\n\n### Dynamic Route Parameters\n\nYou can define routes with dynamic parameters:\n\n```javascript\n{\n  name: 'profile',\n  path: 'profile/:id',\n  component: lazy(() =\u003e import('./Profile')),\n}\n```\n\nAccess these parameters in your component:\n\n```jsx\nconst Profile = createElement({\n  render() {\n    const router = useRouter();\n    const id = router.params.get('id');\n\n    return \u003ch1\u003eProfile ID: {id}\u003c/h1\u003e;\n  },\n});\n```\n\n### Wildcard Routes\n\nWildcard routes in Bullet offer powerful flexibility for handling various routing scenarios. They're particularly useful for handling 404 (Not Found) pages, error pages, and more.\n\n```javascript\n{\n  name: 'not-found',\n  path: '*',\n  component: lazy(() =\u003e import('./NotFound')),\n}\n```\n\nThis routing system provides a robust foundation for creating single-page applications with Bullet. It offers easy navigation, code splitting capabilities, and flexible route configurations to suit various application needs.\n\n---\n\n## Why bullet?\n\nGreat question.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadebola-io%2Fbullet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadebola-io%2Fbullet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadebola-io%2Fbullet/lists"}