{"id":22014063,"url":"https://github.com/beeplin/hyper-arrow","last_synced_at":"2025-05-06T22:15:03.890Z","repository":{"id":253546886,"uuid":"843806835","full_name":"beeplin/hyper-arrow","owner":"beeplin","description":"super tiny front-end framework, hyperscript for templating, arrow functions for reactivity","archived":false,"fork":false,"pushed_at":"2025-04-06T03:14:44.000Z","size":599,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-06T22:14:54.911Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/beeplin.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-08-17T13:32:42.000Z","updated_at":"2025-04-06T03:14:47.000Z","dependencies_parsed_at":"2024-08-29T15:19:36.471Z","dependency_job_id":"0d156f52-33e3-4b3a-8dcd-9a2de420d9dd","html_url":"https://github.com/beeplin/hyper-arrow","commit_stats":null,"previous_names":["beeplin/h-arrow","beeplin/hyper-arrow"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beeplin%2Fhyper-arrow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beeplin%2Fhyper-arrow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beeplin%2Fhyper-arrow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beeplin%2Fhyper-arrow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beeplin","download_url":"https://codeload.github.com/beeplin/hyper-arrow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252776600,"owners_count":21802469,"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":[],"created_at":"2024-11-30T03:26:05.570Z","updated_at":"2025-05-06T22:15:03.862Z","avatar_url":"https://github.com/beeplin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hyper-arrow\n\n[中文版](./README.zh-CN.md)\n\nsuper tiny front-end UI library, for educational purposes\n\n- **~4KB** minified, **~2KB** gzipped\n- **ZERO** dependencies\n- **100%** test coverage, reliable\n- No building steps, easy use via `\u003cscript module=\"type\"\u003e` tag in plain HTML\n- Proxy-based reactivity, like [`reactive` in Vue 3](https://vuejs.org/api/reactivity-core.html#reactive) or [`makeAutoObservable` in MobX](https://mobx.js.org/observable-state.html#makeautoobservable)\n- No templates or JSX. Tag functions `div`, `button` and etc. work like [`h` in hyperscript](https://github.com/hyperhype/hyperscript) or [`h` in Vue 3](https://vuejs.org/api/render-function.html#h)\n- `=\u003e` arrow functions within tag functions provide reactivity, which is how the name comes ;)\n- Smart and performant element children inserting, removing, swapping and updating, if all children has unique `id` attributes\n\n## Get started\n\n```js\n// @ts-nocheck\nimport { mount, reactive, tags } from '../../hyper-arrow.js'\n\nclass Model {\n  input = ''\n  list = []\n  add() {\n    this.list.push(this.input)\n    this.input = ''\n  }\n  clear() {\n    this.list = []\n  }\n}\n\n// create a reactive object\nconst model = reactive(new Model())\n\n// design your view with nested HTML tag functions\nconst { button, div, input, li, ul } = tags.html\n\nconst view = div(\n  // element properties in the first parameter\n  {\n    id: 'container-id',\n    class: 'container-class',\n    style: 'padding: 4px;',\n  },\n  // children in the rest parameters\n  div({ style: 'margin: 4px' }, 'hyper-arrow demo'),\n  input({\n    type: 'text',\n    // arrow functions make properties reactive\n    value: () =\u003e model.input,\n    class: () =\u003e (model.input ? 'class3' : 'class4'),\n    // your can set inline styles here with prefix '$'\n    $margin: '4px',\n    // again, arrow function for reactive style\n    $color: () =\u003e (model.input.length \u003e 5 ? 'red' : 'black'),\n    // event listeners with prefix 'on'\n    onInput(event) {\n      model.input = event.target.value\n    },\n    onKeydown(event) {\n      if (event.code === 'Enter') model.add()\n    },\n  }),\n  button(\n    {\n      type: 'button',\n      style: 'margin: 4px',\n      onClick() {\n        model.add()\n      },\n    },\n    'add',\n  ),\n  // can also using 'innerText' to set text as single child\n  // just like `el.innerText = 'xxx'` in DOM API\n  button({\n    type: 'button',\n    innerText: 'clear all',\n    style: () =\u003e 'margin: 4px;',\n    onClick() {\n      model.clear()\n    },\n  }),\n  // the first element properties can be omitted, if none exists\n  ul(\n    // children can also be an arrow function, also reactive\n    () =\u003e model.list.map((item) =\u003e li(item)),\n  ),\n)\n\n// mount your view to the page and go!\nmount('#app', view)\n\nmodel.input = 'aaa'\nmodel.add()\nmodel.input = 'bbb'\nmodel.add()\n```\n\nIt will create the following DOM tree with proper dynamic behaviors:\n\n```html\n\u003cdiv id=\"container-id\" class=\"container-class\" style=\"padding: 4px;\"\u003e\n  \u003cdiv style=\"margin: 4px;\"\u003ehyper-arrow demo\u003c/div\u003e\n  \u003cinput type=\"text\" class=\"class4\" style=\"margin: 4px; color: black;\" /\u003e\n  \u003cbutton type=\"button\" style=\"margin: 4px;\"\u003eadd\u003c/button\u003e\n  \u003cbutton type=\"button\" style=\"margin: 4px;\"\u003eclear all\u003c/button\u003e\n  \u003cul\u003e\n    \u003cli\u003eaaa\u003c/li\u003e\n    \u003cli\u003ebbb\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/div\u003e\n```\n\nSee `src/examples` for more.\n\n## Basic API\n\n### `reactive(object)`\n\nCreate a reactive proxy for any `object`, and then it can be used in tag functions.\n\n### `tags`\n\nAll HTML tag functions are in `tags.html`. `tags.svg` contains SVG tag functions, and `tags.mathml` contains MathML tag functions.\n\n```js\nimport { mount, reactive, tags } from '../../hyper-arrow.js'\n\nconst { div, button } = tags.html\nconst { svg, circle } = tags.svg\nconst { math, mi, mn, mfrac } = tags.mathml\n\nconst model = reactive({ number: 10 })\n\n// children can be an array, instead of being the rest parameters\nconst view = div({ id: 'root' }, [\n  button({\n    innerText: 'increase',\n    onClick() {\n      model.number++\n    },\n  }),\n  // if you have single-line props, then children as array formats better\n  svg({ stroke: 'red', fill: 'lightyellow' }, [\n    circle({ cx: '50', cy: '50', r: () =\u003e model.number.toString() }),\n  ]),\n  // same here, children in array\n  math({ display: 'block' }, [\n    // here children are not in array. writes easier and looks better\n    mfrac(\n      mi('x'),\n      mn(() =\u003e model.number.toString()),\n    ),\n  ]),\n])\n\nmount('#app', view)\n```\n\nIt generates the DOM tree:\n\n```html\n\u003cdiv id=\"root\"\u003e\n  \u003cbutton\u003eincrease\u003c/button\u003e\n  \u003csvg stroke=\"red\" fill=\"lightyellow\"\u003e\n    \u003ccircle cx=\"50\" cy=\"50\" r=\"10\"\u003e\u003c/circle\u003e\n  \u003c/svg\u003e\n  \u003cmath display=\"block\"\u003e\n    \u003cmfrac\u003e\u003cmi\u003ex\u003c/mi\u003e\u003cmn\u003e10\u003c/mn\u003e\u003c/mfrac\u003e\n  \u003c/math\u003e\n\u003c/div\u003e\n```\n\n### `mount(element_selector, view, [options])`\n\nMount the view onto DOM. Examples already shown above. See below for details of optional `options`\n\n## Advanced API\n\n### `UID_ATTR_NAME`\n\n`mount` can accept an optional third parameter `options` for extra configuration.\n\n`[UID_ATTR_NAME]` is a unique symbol key in `mount`'s `options`. It adds unique HTML attributes to all DOM elements created by `mount` in order to identify themselves.\n\n```js\nimport { mount, tags, UID_ATTR_NAME } from '../../hyper-arrow.js'\n\nconst { div } = tags.html\n\nconst view = div(div('a'), div('b'), div(div('c'), div('d')), div('e'))\n\nmount('#app', view, { [UID_ATTR_NAME]: 'uid' })\n```\n\nwill generate:\n\n```html\n\u003cdiv uid=\"0\"\u003e\n  \u003cdiv uid=\"1\"\u003ea\u003c/div\u003e\n  \u003cdiv uid=\"2\"\u003eb\u003c/div\u003e\n  \u003cdiv uid=\"3\"\u003e\n    \u003cdiv uid=\"4\"\u003ec\u003c/div\u003e\n    \u003cdiv uid=\"5\"\u003ed\u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv uid=\"6\"\u003ee\u003c/div\u003e\n\u003c/div\u003e\n```\n\nThis is useful, for example, when checking if the parent element, when doing smart children updating or caching, is reusing elements correctly instead of recreating new ones (see below).\n\n### `CACHE_REMOVED_CHILDREN`\n\nA unique symbol key that indicates how many removed children elements a parent DOM element can cache, so instead of creating new children, it can reuse the cached ones when needed, as long as the children's `id` attributes match.\n\n```js\nimport {\n  CACHE_REMOVED_CHILDREN,\n  mount,\n  reactive,\n  tags,\n  UID_ATTR_NAME,\n} from '../../hyper-arrow.js'\n\nconst { div, button, ul, li } = tags.html\n\nconst model = reactive({ list: ['0', '1'] })\n\nconst view = div(\n  button({\n    innerText: 'change',\n    onClick() {\n      const length = Math.floor(Math.random() * 10)\n      model.list = Array.from({ length }, (_, i) =\u003e i.toString())\n    },\n  }),\n  // allows cache, with 100 as max cache size\n  ul({ id: 'list', [CACHE_REMOVED_CHILDREN]: 100 }, () =\u003e\n    model.list.map((item) =\u003e li({ id: () =\u003e item }, item.toString())),\n  ),\n)\n\nmount('#app', view, { [UID_ATTR_NAME]: 'uid' })\n```\n\nIn the dev tool you can see that, when the list changes, the `uid` attributes of `li` elements remain the same. That shows `ul` is reusing old removed `li`s.\n\n### `ON_CREATE`\n\nA unique symbol key to create a special \"onCreate\" event handler on a DOM element.\n\n```js\nimport { mount, ON_CREATE, tags } from '../../hyper-arrow.js'\n\nconst { input } = tags.html\n\nmount(\n  '#app',\n  input({\n    value: 'hello world',\n    [ON_CREATE](el) {\n      requestAnimationFrame(() =\u003e {\n        el.focus()\n        setTimeout(() =\u003e {\n          el.select()\n        }, 1000)\n      })\n    },\n  }),\n)\n```\n\nThe created DOM element, `el`, is passed into the event handler function.\n\n### `isReactive(object)`\n\nCheck if an `object` is a reactive proxy.\n\n### `watch(fn, [effectFn])`\n\nRun `fn()` once, and whenever `fn`'s dependencies (see below) change, automatically rerun `fn()`, or, if `effectFn` provided, run `effectFn(fn())`.\n\n### `fac2opas`\n\n`Map\u003cFunctionAndContext, WeakMap\u003cObject, Set\u003cProperty\u003e\u003e\u003e`. For each **function-and-context** (**FAC**), `fac2opas` stores all the **object-property-access**es (**OPA**s) appearing within the function call. When any **OPA** changes, the corresponding function of the **FAC** reruns, and with the help of its contextual info, updates the correct position of the DOM as its new returned value. `fn`s of `watch`s also go into `fac2opas`.\n\nKeep in mind that your **FAC**s' returned value must rely only on reactive **OPA**s (like `o.p` or `o[p]`) within the **FAC**, not on any other things like non-reactive object, free variable bindings (like `let x = 1` inside the function), or global/closure variables.\n\n## Reactive Implementation Details\n\nThe reactive system is one of hyper-arrow's core features. Let's dive into how it works.\n\n### Basic Concepts\n\nIn hyper-arrow, there are three ways to define reactive properties:\n\n```javascript\nconst model = reactive({\n  count: 0,\n  text: 'hello',\n})\n\nconst view = div(\n  // 1. Arrow function (recommended)\n  { textContent: () =\u003e model.text },\n\n  // 2. Regular function\n  {\n    textContent: function () {\n      return model.text\n    },\n  },\n\n  // 3. Method shorthand\n  {\n    textContent() {\n      return model.text\n    },\n  },\n)\n```\n\n### Why Functions?\n\nThe reactive system works through following steps:\n\n1. **Dependency Collection**\n\nSimplified implementation:\n\n```javascript\nfunction reactive(obj) {\n  return new Proxy(obj, {\n    get(target, key) {\n      // When executing a function, record which OPA it depends on\n      if (currentFac) {\n        trackDependency(currentFac, target, key)\n      }\n      return target[key]\n    },\n  })\n}\n```\n\n2. **Function Execution**\n\n```javascript\nfunction runFac(fac) {\n  const fn = fac[2] // Get function\n  currentFac = fac // Mark currently executing function\n  const result = fn() // Execute function, trigger proxy.get, collect dependencies\n  currentFac = null\n  return result\n}\n```\n\n3. **Update Triggering**\n\nWhen reactive object property changes:\n\n- System finds all functions depending on this property\n- Reruns these functions\n- Updates DOM with new return values\n\n### Dependency Tracking Mechanism\n\nhyper-arrow uses the following data structure to track dependencies:\n\n```typescript\n// Store each function's dependencies\nexport const fac2opas = new Map\u003cFac, WeakMap\u003cobject, Set\u003cproperty\u003e\u003e\u003e()\n```\n\n#### Dependency Collection Process\n\n1. Set `currentFac` when executing reactive function\n2. Accessing reactive property during function execution triggers Proxy's get interceptor\n3. Get interceptor records dependency between current function and accessed property\n\n#### Update Process\n\n1. Modifying reactive object property triggers Proxy's set interceptor\n2. Look up all functions depending on this property\n3. Rerun these functions\n4. Update corresponding DOM elements\n\n### Why Arrow Functions Recommended?\n\n1. **Conciseness**: More concise syntax, easier to read\n2. **this binding**: Avoids this binding issues\n3. **Design intent**: Clearly expresses this is a reactive property\n\n### Practical Example\n\n```javascript\nimport { reactive, div, mount } from 'hyper-arrow'\n\n// Create reactive data\nconst model = reactive({\n  count: 0,\n  message: 'Hello',\n})\n\n// Create view\nconst view = div({\n  class: () =\u003e (model.count \u003e 0 ? 'active' : ''),\n  textContent: () =\u003e `${model.message} (${model.count})`,\n})\n\n// Mount to DOM\nmount('#app', view)\n\n// Data changes automatically trigger view updates\nmodel.count++\nmodel.message = 'Hi'\n```\n\n### Summary\n\n1. Any form of function can be used for reactive properties\n2. Arrow functions are recommended but not required\n3. Core of reactive system is dependency collection and automatic updates\n4. Functions are used to track property access and rerun when needed\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeeplin%2Fhyper-arrow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeeplin%2Fhyper-arrow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeeplin%2Fhyper-arrow/lists"}