{"id":16884391,"url":"https://github.com/tornqvist/yeet","last_synced_at":"2025-03-17T06:31:34.851Z","repository":{"id":45990976,"uuid":"328949803","full_name":"tornqvist/yeet","owner":"tornqvist","description":"Teeny-weeny front end framework","archived":false,"fork":false,"pushed_at":"2024-07-28T12:04:16.000Z","size":565,"stargazers_count":24,"open_issues_count":3,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-27T19:03:24.014Z","etag":null,"topics":["app","dom","framework","frontend","html","javascript","template","ui","vanilla"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/tornqvist.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,"publiccode":null,"codemeta":null}},"created_at":"2021-01-12T10:25:31.000Z","updated_at":"2023-08-05T22:45:23.000Z","dependencies_parsed_at":"2024-10-27T12:11:53.744Z","dependency_job_id":"a1b1a657-f976-4883-912e-6c856c687a1d","html_url":"https://github.com/tornqvist/yeet","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tornqvist%2Fyeet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tornqvist%2Fyeet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tornqvist%2Fyeet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tornqvist%2Fyeet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tornqvist","download_url":"https://codeload.github.com/tornqvist/yeet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243847062,"owners_count":20357317,"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":["app","dom","framework","frontend","html","javascript","template","ui","vanilla"],"created_at":"2024-10-13T16:27:45.457Z","updated_at":"2025-03-17T06:31:34.480Z","avatar_url":"https://github.com/tornqvist.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg alt=\"yeet\" width=\"404\" src=\"https://user-images.githubusercontent.com/985518/112662961-91d73b00-8e58-11eb-8cbe-c9e87d505c74.jpg\"\u003e\n  \u003cp\u003eTeeny-weeny front end framework \u003c/p\u003e\n  \u003c!-- Stability --\u003e\n  \u003ca href=\"https://nodejs.org/api/documentation.html#documentation_stability_index\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square\"\n      alt=\"API stability\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- NPM version --\u003e\n  \u003ca href=\"https://npmjs.org/package/yeet\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/yeet/next.svg?style=flat-square\"\n      alt=\"NPM version\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Build Status --\u003e\n  \u003ca href=\"https://github.com/tornqvist/yeet/actions\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/workflow/status/tornqvist/yeet/CI?style=flat-square\"\n      alt=\"Build Status\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Downloads --\u003e\n  \u003ca href=\"https://npmjs.org/package/yeet\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dt/yeet?style=flat-square\"\n      alt=\"Download\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Standard --\u003e\n  \u003ca href=\"https://standardjs.com\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square\"\n      alt=\"Standard\" /\u003e\n  \u003c/a\u003e\n  \u003chr\u003e\n\u003c/div\u003e\n\n## Features\n- **No transpilation** – It's all plain vanilla JavaScript\n- **Small size** – Weighing in at `3kb`, you'll barely notice it\n- **Minimal API** – Only a handfull functions to learn\n- **No magic** – Prototypal state and events\n- **It's fast** – Both on server and client\n\n## Example\n```js\nimport { html, mount, use, Component } from 'https://cdn.skypack.dev/yeet@next'\n\nmount('body', Component(App))\n\nfunction App (state, emit) {\n  use(store)\n\n  return function () {\n    return html`\n      \u003cbody\u003e\n        \u003cp\u003eClicked ${state.count} times\u003c/p\u003e\n        \u003cbutton onclick=${() =\u003e emit('increment'))}\u003eClick me\u003c/button\u003e\n      \u003c/body\u003e\n    `\n  }\n}\n\nfunction store (state, emitter) {\n  state.count = 0\n  emitter.on('increment', function () {\n    state.count++\n    emitter.emit('render')\n  })\n}\n```\n\n## Why yeet?\nBuilding interactive and performant websites shouldn't require a whole lot of\ndependencies, a bundler, or even Node.js for that matter. The JavaScript\nlanguage has all the capabilities required built right in, without sacrificing\neither developer or user experience.\n\nFrameworks are tools and tools should be interchangeable and easy to replace.\nThat's why yeet rely on the lowest common denominator – the DOM. There are no\nunneccessary abstractions such as virtual DOM, synthetic events or template\nsyntax to learn. Only functions and prototypes.\n\nIf you know JavaScript you already know most there is to know about yeet. And\nanything new you learn from using yeet is directly benefitial to anything else\nyou might want to use JavaScript for.\n\n## Prototypal state\nThe state object in yeet is shared between components using prototypes. You can\nthink of the state object as a shared context which components can use to read\nfrom and write to.\n\nHowever, a component can only ever mutate its own state, it can only read from\nthe parent state, yet they are the same object – what?! This is achieved using\nprototypes. The prototype of a component's state object is the parent\ncomponent's state object.\n\n\u003cdetails\u003e\n\u003csummary\u003eAbout prototypal inheritance\u003c/summary\u003e\n\nJavaScript prototypes are the mechanism for inheriting properties and behavior\nfrom one object to another. What is facinating about prototypes is that they\nare live – meaning that any change made to an object is immediately made\navailable to all other objects whose prototype chain includes said object.\n\n```js\nconst parent = {}\nconst child = Object.create(parent)\n\nparent.name = 'world'\nconsole.log(`Hello ${parent.name}`) // Hello world\nconsole.log(`Hello ${child.name}`) // Hello world\n\nchild.name = 'planet'\nconsole.log(`Hello ${parent.name}`) // Hello world\nconsole.log(`Hello ${child.name}`) // Hello planet\n```\n\nRead more about [Object prototypes][Object prototypes].\n\n\u003c/details\u003e\n\nTo modify a parent state object, one can use events to communicate up the\ncomponent tree (or prototype chain, if you will).\n\n## Events\nEvents are the core mechanism for communication up the component tree. Yeet\nadhers to the dogma \"data down, events up\", which is to say that data should be\npassed down the component tree, either with state or as arguments. When\nsomething happens, e.g. the user clicks a button, an event should be emitted\nwhich bubbles up the component tree, notifying components which may then mutate\ntheir state and issue a re-render.\n\n## Components\nComponents can be usefull in situations when you need a locally contained state,\nwant to use some third party library or want to know when components mount or\nunmout in the DOM.\n\nComponents in yeet use [generator functions][generator functions] to control the\ncomponent lifecycle. By using generators yeet can step through your component\nand pause execution until the appropiate time, e.g. when the component has\nupdated or is removed from the DOM. This allows you to retain local variables\nwhich persist throughout the component lifespan without meddling with `this` or\nlearning new state management techinques, they're just regular ol' variables.\n\n```js\nimport { html, ref, mount, Component } from 'https://cdn.skypack.dev/yeet@next'\nimport mapboxgl from 'https://cdn.skypack.dev/mapbox-gl'\n\nconst state = { center: [18.0704503, 59.3244897] }\n\nmount('#app', Component(Map), state)\n\nfunction * Map (state, emit) {\n  const container = ref()\n  let map\n\n  yield function * () {\n    yield html`\u003cdiv id=\"app\" ref=${container}\u003e\u003c/div\u003e`\n\n    map = map || new mapboxgl.Map({\n      container: container.current,\n      center: state.center\n    })\n  }\n\n  map.destroy()\n}\n```\n\n### Generators\nUsing generators allows you to keep local variables accessible throughout the\ncomponent lifecycle. If you are already familiar with generators there's not\nreally that much to learn.\n\nIf you are new to generators, learning yeet will only further build your\nJavaScript toolset, there is nothing here which you cannot use in other\ncontexts.\n\nA generator function is a special kind of function which can pause execution\nmidway and allows us to inspect intermediate values before procceding with\nexecution. A generator function has two caracteristics which set it appart from\nregular functions, and asterics (`*`) after the `function` keyword and the\n`yield` keyword.\n\n\u003cdetails\u003e\n\u003csummary\u003eThe anatomy of a generator function\u003c/summary\u003e\n\n```js\n//       ↓ This thing makes it a generator function\nfunction * createGenerator (list) {\n  for (const num of list) {\n    yield num // ← Pause here\n  }\n  return 'finished!'\n}\n\n//                ↓ Call it like any other function\nconst generator = createGenerator([1, 2, 3])\n\n// We can now step through the generator\ngenerator.next() // { value: 1, done: false }\ngenerator.next() // { value: 2, done: false }\ngenerator.next() // { value: 3, done: false }\ngenerator.next() // { value: 'finished!', done: true }\n```\n\n\u003c/details\u003e\n\nBy yielding in a yeet component you are telling yeet to halt execution and save\nthe rest of the function for later, e.g. when the component has updated or when\nit is removed from the DOM. A yeet component's lifecycle is thereby clearly laid\nout in chronological order, from top to bottom.\n\n#### Lifecycle\nGenerators are used to declare the lifecycle of yeet components. Only functions,\nhtml partials (returned by the `html` and `svg` tags) and promises carry any\nspecial meaning when using `yield`. When a yeet component yields a function,\nthat is the function which will be used for any consecutive re-renders. Anything\nthat comes after `yield` will be executed once the components is removed from\nthe DOM (e.g. replaced by another element).\n\n```js\nfunction * MyComponent () {\n  // Happens only once, during setup\n  yield function () {\n    // Happens every time the component updates\n  }\n  // Happens only once, when the component is removed/replaced\n}\n```\n\nThey yielded function may also be a generator function. This can be used to\nperform side effects such as setting up subscriptions, manually manipulating the\nDOM or initializing some third party library. This is handled asynchrounously,\nmeaning the DOM will have updated and the changes may have been made visible to\nthe user before the generator finishes.\n\n```js\nfunction MyComponent () {\n  return function * () {\n    // Happens before every update\n    yield html`\u003ch1\u003eHello planet!\u003c/h1\u003e`\n    // Happens after every update\n  }\n}\n```\n\nIf you require immediate access to the rendered element, e.g. to _synchronously_\nmutate or inspect the rendered element _before_ the page updates, you may yield\nyet another function.\n\n_Note: Use with causion, this may have a negative impact on performance._\n\n```js\nfunction MyComponent () {\n  return function () {\n    return function * () {\n      // Happens before every update\n      yield html`\u003ch1\u003eHello planet!\u003c/h1\u003e`\n      // Happens SYNCHRONOUSLY after every update\n    }\n  }\n}\n```\n\n#### Arguments (a.k.a. `props`)\nEven though all components have access to the shared state, you'll probably need\nto supply your components with some arguments to configure behavior or forward\nparticular properties. You can either provide extra arguments to the `Component`\nfunction or you can call the function returned by `Component` with any number of\narguments.\n\n```js\nfunction Reaction (state, emit) {\n  // ↓ Arguments are provided to the inner function\n  return function ({ emoji }) {\n    return html`\u003cbutton onclick=${() =\u003e emit('reaction', emoji)}\u003e${emoji}\u003c/button\u003e`\n  }\n}\n\n// ↓ Declare component on beforehand\nconst ReactionComponent = Component(Reaction)\n\n// ↓ Declare component and arguments on beforehand\nconst SadReaction = Component(Reaction, { emoji: '😢' })\n\nhtml`\n  \u003cform\u003e\n    ${Component(Reaction, { emoji: '😀' })} \u003c!-- ← Declare component inline --\u003e\n    ${ReactionComponent({ emoji: '😐' })}\n    ${SadReaction}\n  \u003c/form\u003e\n`\n```\n\n### Async components\nComponents can yield any value but if you yield a Promise yeet will await the\npromise before it continues to render. On the server, rendering is asynchronous by\ndesign, this means that all promises are resolved as the component renders.\nRendering in the browser behaves a little differently. While awaiting a promise\nnothing will be rendered in place of the component. Once all yielded promises\nhave resolved (or rejected) the component will finish rendering and the element\nwill appear on the page.\n\nYeet does not make any difference between promises which resolve or reject, you\nwill have to catch and handle rejections accordingly, yeet will just forward the\nresolved or rejected value.\n\n```js\nimport fetch from 'cross-fetch'\nimport { html, use } from 'yeet'\n\nfunction User (state, emit) {\n  const get = use(api) // ← Register api store with component\n  return function () {\n    //           ↓ Expose the promise to yeet\n    const user = yield get(`/users/${state.user.id}`)\n    return html`\n      \u003cbody\u003e\n        \u003ch1\u003e${user.name}\u003c/h1\u003e\n      \u003c/body\u003e\n    `\n  }\n}\n\nfunction api (state, emit) {\n  if (!state.cache) state.cache = {} // ← Use existing cache if available\n\n  //     ↓ Return a function for lazily reading from the cache\n  return function (url) {\n    if (url in state.cache) return state.cache[url] // ← Read from cache\n    return fetch(url).then(async function (res) {\n      const data = await data.json()\n      state.cache[url] = data // ← Store response in cache\n      return data // ← Return repsonse\n    })\n  }\n}\n```\n\n#### Lists and Keys\nIn most situations yeet does an excellent job at keeping track of which\ncomponent goes where. This is in part handled by identifying which template tags\n(the `html` and `svg` tag functions) are used. In JavaScript, template\nliterals are unique and yeet leverages this to keep track of which template tag\ngoes where.\n\nWhen it comes to components, yeet uses your component function as a unique key to\nkeep track of which component is tied to which element in the DOM.\n\nWhen it comes to lists of identical components, this becomes difficult and yeet\nneeds a helping hand in keeping track. In these situations, you can provide a\nunique `key` to each component which will be used to make sure that everything\nkeeps running smoothly.\n\n```js\nfunction Exponential (state, emit) {\n  let exponent = 1\n\n  function increment () {\n    exponent++\n    emit('render')\n  }\n\n  return function ({ num }) {\n    return html`\n      \u003cli\u003e\n        \u003cbutton onclick=${increment}\u003e${Math.pow(num, exponent)}\u003c/button\u003e\n      \u003c/li\u003e\n    `\n  }\n}\n\nconst numbers = [1, 2, 3, 4, 5]\nreturn html`\n  \u003col\u003e\n    ${numbers.map((num) =\u003e Component(Exponential, { num, key: num }))}\n  \u003c/ol\u003e\n`\n```\n\n### Stores\nStores are the mechanism for sharing behavior between components, or even apps.\nA store can subscribe to events, mutate the local state and issue re-renders.\n\n```js\nimport { html, use, Component } from 'https://cdn.skypack.dev/yeet@next'\n\nfunction Parent (state, emit) {\n  use(counter) // ← Use the counter store with this component\n\n  return function () {\n    return html`\n      ${Component(Increment)}\n      \u003coutput\u003e${state.count}\u003c/output\u003e\n      ${Component(Decrement)}\n    `\n  }\n}\n\nfunction Increment (state, emit) {\n  return html`\u003cbutton onclick=${() =\u003e emit('increment')}\u003e+\u003c/button\u003e`\n}\n\nfunction Decrement (state, emit) {\n  return html`\u003cbutton onclick=${() =\u003e emit('decrement')}\u003e-\u003c/button\u003e`\n}\n\nfunction counter (state, emitter) {\n  state.count = 0 // ← Define some initial state\n\n  emitter.on('increment', function () {\n    state.count++\n    emitter.emit('render')\n  })\n\n  emitter.on('decrement', function () {\n    state.count--\n    emitter.emit('render')\n  })\n}\n```\n\n#### Events\nHow you choose to name your events is entirely up to you. There's only one\nexception: the `render` event has special meaning and will re-render the closest\ncomponent in the component tree. The `render` event does not bubble.\n\n## Server rendering (SSR)\nYeet has first-class support for server rendering. There are plans to support\nserver-rendered templates, meaning any backend could render the actual HTML and\nyeet would wire up functionality using the pre-existing markup.\n\nRendering on the server supports fully asynchronous components. If a component\nyields promises, yeet will wait for these promises to resolve while rendering.\n\n### Server rendered templates (non-Node.js)\n_Coming soon…_\n\n## API\nThe API is intentionally small.\n\n### html\nCreate html partials which can be rendered to DOM nodes (or strings in Node.js).\n\n```js\nimport { html } from 'https://cdn.skypack.dev/yeet@next'\n\nconst name = 'planet'\nhtml`\u003ch1\u003eHello ${name}!\u003c/h1\u003e`\n```\n\n#### Attributes\nBoth literal attributes as well as dynamically \"spread\" attributes work. Arrays\nwill be joined with an empty space (` `) to make it easier to work with many\nspace separated attributes, e.g. `class`.\n\n```js\nimport { html } from 'https://cdn.skypack.dev/yeet@next'\n\nconst attrs = { disabled: true, hidden: false, placeholder: null }\nhtml`\u003cinput type=\"text\" class=\"${['foo', 'bar']}\" ${attrs}\u003e`\n// → \u003cinput type=\"text\" class=\"foo bar\" disabled\u003e\n```\n\n##### Events\nEvents can be attached to elements using the standard `on`-prefix.\n\n```js\nimport { html } from 'https://cdn.skypack.dev/yeet@next'\n\nhtml`\u003cbutton onclick=${() =\u003e alert('You clicked me!')}\u003eClick me!\u003c/button\u003e`\n```\n\n#### Arrays\nIf you have lists of things you want to render as elements, interpolating arrays\nworks just like you'd expect.\n\n```js\nimport { html } from 'https://cdn.skypack.dev/yeet@next'\n\nconst list = [1, 2, 3]\nhtml`\u003col\u003e${list.map((num) =\u003e html`\u003cli\u003e${num}\u003c/li\u003e`)}\u003c/ol\u003e`\n```\n\n#### Fragments\nIt's not always that you can or need to have an outer containing element.\nRendering fragments works just like single container elements.\n\n```js\nimport { html } from 'https://cdn.skypack.dev/yeet@next'\n\nhtml`\n  \u003ch1\u003eHello world!\u003c/h1\u003e\n  \u003cp\u003eLorem ipsum dolor sit amet…\u003c/p\u003e\n`\n```\n\n### svg\nThe `svg` tag is required for rendering all kinds of SVG elements, such as\n`\u003csvg\u003e`, `\u003cpath\u003e`, `\u003ccircle\u003e` etc. All the same kinds of behaviors as described in\n[`html`](#html) apply to `svg`.\n\n```js\nimport { svg } from 'https://cdn.skypack.dev/yeet@next'\n\nsvg`\n  \u003csvg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\"\u003e\n    \u003ccircle cx=\"50\" cy=\"50\" r=\"50\"/\u003e\n  \u003c/svg\u003e\n`\n```\n\n### raw\nIf you have preformatted html that you wish to render, just interpolating them\nin the template won't work. Text that is interpolated in templates is\nautomatically escaped to avoid common [XXS attacks][xxs], e.g. injecting script\ntags.\n\n```js\nimport { html, raw } from 'https://cdn.skypack.dev/yeet@next'\n\nconst content = '\u003cstrong\u003eHello world!\u003c/strong\u003e'\n\nhtml`\u003cdiv\u003e${content}\u003c/div\u003e`\n// → \u003cdiv\u003e\u0026lt;strong\u0026gt;Hello world!\u0026lt;/strong\u0026gt;\u003c/div\u003e\n\nhtml`\u003cdiv\u003e${raw(content)}\u003c/div\u003e`\n// → \u003cdiv\u003e\u003cstrong\u003eHello world!\u003c/strong\u003e\u003c/div\u003e\n```\n\n### ref\nIt's common to want to access elements in the DOM to mutate or read properties.\nFor this there is the `ref` helper which, when called, will return an object\nwith the property `current` which will be the currently mounted DOM node it was\nattached to.\n\n_Note: This only works in the client, `current` will never be available while\nserver rendering._\n\n```js\nimport { html, ref, render } from 'https://cdn.skypack.dev/yeet@next'\n\nconst div = ref()\nrender(html`\u003cdiv ref=${div}\u003eHello planet!\u003c/div\u003e`)\n\ndiv.current // ← Reference to the rendered div element\n```\n\n### use\nRegister a store to use with component. Accepts a function which will be called\nwith `state` and `emitter` (an instance of [`EventEmitter`](#eventemitter)).\nWhatever is returned by the supplied function is returned by `use`. You should\nrefrain from using `use` anywhere but during the component setup stage.\n\nStores are great for sharing functionality between components. A shared store\ncan be used to handle common operations on the shared state object or just to\navoid duplicating code between components.\n\n```js\nimport { html, use, ref } from 'https://cdn.skypack.dev/yeet@next'\n\nfunction * Video (state, emit) {\n  const video = ref()\n  const detach = use(pauser(video))\n\n  yield ({ src }) =\u003e html`\u003cvideo src=\"${src}\" ref=${video}\u003e\u003c/video\u003e`\n\n  detach()\n}\n\nfunction pauser (video) {\n  return function (state, emitter) {\n    function onvisibilitychange () {\n      if (document.visibilityState === 'visible') {\n        video.current.play()\n      } else {\n        video.current.pause()\n      }\n    }\n\n    document.addEventListener('visibilitychange', onvisibilitychange)\n\n    return function () {\n      document.removeEventListener('visibilitychange', onvisibilitychange)\n    }\n  }\n}\n```\n\n### mount\nMount a given html partial on a DOM node. Accepts a html partial, a DOM node or\nselector and optionally a root state object.\n\n```js\nimport { html, mount } from 'https://cdn.skypack.dev/yeet@next'\n\nmount('body', html`\n  \u003cbody\u003e\n    \u003ch1\u003eHello planet!\u003c/h1\u003e\n  \u003c/body\u003e\n`)\n```\n\n```js\nimport { html, mount, Component } from 'https://cdn.skypack.dev/yeet@next'\n\nmount(document.getElementById('app'), Component(Main), { name: 'world' })\n\nfunction Main (state, emit) {\n  return html`\n    \u003cmain id=\"app\"\u003e\n      \u003ch1\u003eHello ${state.name}!\u003c/h1\u003e\n    \u003c/main\u003e\n  `\n}\n```\n\n### render\nRender a partial to element (browser) or string (server). On the client, render\nis synchronous and the resulting DOM node is returned. On the server `render`\nalways returns a promise which resolves to a string. Accepts an optional root\nstate object.\n\n```js\nimport { html, render } from 'https://cdn.skypack.dev/yeet@next'\n\nconst h1 = render(html`\u003ch1\u003eHello planet!\u003c/h1\u003e`))\ndocument.body.appendChild(h1)\n```\n\n```js\nimport { html, render } from 'yeet'\nimport { createServer } from 'http'\n\ncreateServer(async function (req, res) {\n  const body = await render(html`\u003cbody\u003eHello world!\u003c/body\u003e`)\n  res.end(body)\n}).listen(8080)\n```\n\n```js\nimport { Readable } from 'stream'\nimport { html, render } from 'yeet'\nimport { createServer } from 'http'\n\ncreateServer(async function (req, res) {\n  Readable.from(html`\u003cbody\u003eHello world!\u003c/body\u003e`).pipe(res)\n}).listen(8080)\n```\n\n### Component\nThe Component function accepts a function as its first argument and any number\nof additional arguments. The additional arguments will be forwarded to the inner\nrender function. The Component function returns a function which may be called\nwith any number of arguments, these arguments will override whichever arguments\nwere supplied prior.\n\nIt is best practice to provide an object as the first render argument since the\noptional `key` property is extracted from the first render argument.\n\n```js\nimport { html, render, Component } from 'https://cdn.skypack.dev/yeet@next'\n\nfunction Greeting () {\n  return function (props, name = 'world') {\n    return html`\u003cp\u003e${props?.phrase || 'Hello'} ${name}!\u003c/p\u003e`\n  }\n}\n\nrender(Component(Greeting))\n// → \u003cp\u003eHello world!\u003c/p\u003e\n\nrender(Component(Greeting, { phrase: 'Hi' }))\n// → \u003cp\u003eHi world!\u003c/p\u003e\n\nrender(Component(Greeting, { phrase: 'Howdy' }, 'planet'))\n// → \u003cp\u003eHowdy planet!\u003c/p\u003e\n\nconst Greeter = Component(Greeting)\nrender(Greeter({ phrase: 'Nice to meet you,' }))\n// → \u003cp\u003eNice to meet you, world!\u003c/p\u003e\n```\n\n### EventEmitter\nStores are called with state and an event emitter. The event emitter can be used\nto act on events submitted from e.g. user actions. All events except the\n`render` event bubbles up the component tree.\n\nYou can register a catch-all event listener by attaching a listener for the `*`\nevent. The first argument to catch-all listeners is the event name followed by\nthe event arguments.\n\n```js\nemitter.on('*', function (event, ...args) {\n  console.log(`Emitted event \"${event}\" with arguments:`, ...args)\n})\n```\n\n#### `emitter.on(string, Function)`\nAttach listener for the specified event name.\n\n#### `emitter.removeListener(string, Function)`\nRemove the event listener for the specified event name.\n\n#### `emitter.emit(string, ...any)`\nEmit an event of the specified name accompanied by any number of arguments.\n\n## Attribution\nThere wouldn't be a yeet if there hadn't been a [choo][choo]. Yeet borrows a lot\nof the core concepts such as a shared state and event emitter from choo. The\nidea of performant DOM updates based on template literals was born from proof\nof concept work done by [Renée Kooi][goto-bus-stop].\n\n## TODO\n[ ] Server-rendered templates (non-Node.js)\n\n[choo]: https://github.com/choojs/choo\n[goto-bus-stop]: https://github.com/goto-bus-stop\n[Object prototypes]: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes\n[generator functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*\n[xxs]: https://en.wikipedia.org/wiki/Cross-site_scripting\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftornqvist%2Fyeet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftornqvist%2Fyeet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftornqvist%2Fyeet/lists"}