{"id":20285647,"url":"https://github.com/marcisbee/synks","last_synced_at":"2025-04-11T08:40:33.780Z","repository":{"id":53124142,"uuid":"256730502","full_name":"Marcisbee/synks","owner":"Marcisbee","description":"🐉 Synks is a tiny javascript view renderer for generators as components and hooks","archived":false,"fork":false,"pushed_at":"2023-04-05T09:16:30.000Z","size":706,"stargazers_count":7,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T06:50:31.655Z","etag":null,"topics":["components","dom","framework","generators","synks","ui","view"],"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/Marcisbee.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":"2020-04-18T10:57:07.000Z","updated_at":"2023-07-04T08:19:51.000Z","dependencies_parsed_at":"2024-11-14T14:29:42.989Z","dependency_job_id":"2573a2fa-aa53-4efb-b4aa-3bb51aa0cac6","html_url":"https://github.com/Marcisbee/synks","commit_stats":{"total_commits":73,"total_committers":2,"mean_commits":36.5,"dds":0.0547945205479452,"last_synced_commit":"143f50f6795eed9a56fe090f1196922c7dec04c8"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fsynks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fsynks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fsynks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fsynks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Marcisbee","download_url":"https://codeload.github.com/Marcisbee/synks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248362349,"owners_count":21091099,"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":["components","dom","framework","generators","synks","ui","view"],"created_at":"2024-11-14T14:28:04.173Z","updated_at":"2025-04-11T08:40:33.771Z","avatar_url":"https://github.com/Marcisbee.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cimg src='https://user-images.githubusercontent.com/16621507/81100885-f4101000-8f15-11ea-8b68-b47f56f6a473.png' height='100' alt='Synks' /\u003e\n\n**Synks** is a tiny javascript view renderer, that is built async first components.\n\nIt uses JSX/hyperscript for it's templating.\nFunctions, promises and generators for component composition. And classes for contexts.\n\n[![npm version](https://img.shields.io/npm/v/synks.svg?style=flat-square)](https://www.npmjs.com/package/synks)\n[![npm downloads](https://img.shields.io/npm/dm/synks.svg?style=flat-square)](https://www.npmjs.com/package/synks)\n[![npm bundle size (version)](https://img.shields.io/bundlephobia/minzip/synks/latest?style=flat-square)](https://bundlephobia.com/result?p=synks)\n[![discord](https://dcbadge.vercel.app/api/server/a62gfaDW2e?style=flat-square)](https://discord.gg/a62gfaDW2e)\n\n## Installation\n\nTo install the stable version:\n\n```\nnpm install --save synks\n```\n\nThis assumes you are using [npm](https://www.npmjs.com/package/synks) as your package manager.\n\nIf you're not, you can [access these files on unpkg](https://unpkg.com/synks/dist/), download them, or point your package manager to them.\n\n## Documentation\n\n\u003c!-- [Getting started guide](/docs) --\u003e\n\nI'm assuming you already know what JSX is and how to set it up with custom pragma (`Synks.h`), so let's cut the chase and start with component composition.\n\n#### Hello World example\n\nFor simple output we can use simple functional component.\n\n```jsx\nfunction Hello({ what }) {\n  return \u003ch1\u003eHello {what}\u003c/h1\u003e;\n}\n\nSynks.mount(\u003cHello what=\"World\" /\u003e, document.body);\n```\n\nThis example will mount h1 to body like so `\u003cbody\u003e\u003ch1\u003eHello World\u003c/h1\u003e\u003c/body\u003e`\n\n#### Counter example\n\nFor this we'll need to use generators as they can have state inside them.\n\n```jsx\nfunction *Counter() {\n  let count = 0;\n\n  const increment = () =\u003e {\n    count++;\n    this.next();\n  }\n\n  while(true) {\n    yield (\n      \u003cbutton onclick={increment}\u003e\n        {count}\n      \u003c/button\u003e\n    )\n  }\n}\n```\n\n#### Data fetch example\n\nTo handle async data fetching, we can use `async` functional component for this.\n\n```jsx\nasync function Movies() {\n  const movieList = await api.getMovieList();\n\n  return (\n    \u003cul\u003e\n      {movieList.map((movie) =\u003e (\n        \u003cli\u003e{movie.title}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n}\n```\n\n_NOTE:_ This will halt all rendering of it's sibling components.\n\n#### Suspense example\n\nThis will allow sibling components to render even when all of the children's here are not yet ready.\n\n```jsx\nfunction Loading() {\n  return \u003cdiv\u003eLoading...\u003c/div\u003e;\n}\n\nasync function *Suspense({ fallback, children }) {\n  // Here we are saying when fallback is mounted, go to next yield.\n  this.onMount = this.next;\n\n  while (true) {\n    yield fallback;\n    yield (\n      \u003cdiv\u003e{children}\u003c/div\u003e\n    );\n  }\n}\n```\n\nThis will not stop DOM rendering at async components.\n\n#### Context example\n\nAnd finally we can use context to sync multiple components with ony state.\nFor this we need to extend `Synks.Context` class to build our own context.\n\n```jsx\nclass CountContext extends Synks.Context {\n  count = 0;\n  increment() {\n    this.count += 1;\n  }\n}\n```\n\n`count` is a state variable that defaults to `0` and `increment` is a method that increments that count variable. Easy.\n\nNow let's use this context.\n\n```jsx\nSynks.mount(\n  \u003cdiv\u003e\n    \u003cCountContext\u003e\n      \u003cStateCounter /\u003e\n      \u003cdiv\u003e\n        \u003cStateCounter /\u003e\n      \u003c/div\u003e\n    \u003c/CountContext\u003e\n  \u003c/div\u003e\n);\n```\n\nNo extra steps or requirements here, just wrap your context around child components that will use this context.\n\nOk, this is not that hard, but where's the catch? Do I need to do some extra stuff when using context in actual component? Well, no. Let's look at our `StateCounter` we used inside `CountContext`.\n\n```jsx\nfunction *StateCounter() {\n  const countContext = yield CountContext;\n\n  while (true) {\n    yield (\n      \u003cbutton onclick={countContext.increment}\u003e\n        {countContext.count}\n      \u003c/button\u003e\n    )\n  }\n}\n```\n\nOk so when you `yield` a context it automatically returns corresponding context.\n\n_NOTE_: Do not destruct `countContext` as it will be transformed to simple value and not be updatable.\n\nHere's a more in depth example of Contexts: [stackblitz.com/edit/vite-6agmqe](https://stackblitz.com/edit/vite-6agmqe?file=src/main.tsx)\n\n#### Code reuse (mixins vs hooks story)\n\nLet's take for example React hooks. You can create function and reuse that function in multiple components. Hooks can trigger updates, so basically it is extension of component.\n\nOk, lets take a look at how mixins usually work. You create also some kind of a function and then this function or methods of this mixin gets used in component.\n\nSo basically these are kind of 2 different solutions. I might have gone the route of either one of these, but instead I think I've found a solution for this that takes the best of both worlds.\n\nI currently call them hooks internally as they work more like hooks.\n\nIt's just a generator function with ability to get parent components scope.\n\nLet's create our first hook, that will listen to `keypressed` events and increment value accordingly.\n\n```jsx\nfunction* countHook() {\n  /**\n   * First of all lets get scope of parent component.\n   * This will allow us to call `scope.next()` to update\n   * component, just like we do in components.\n   */\n  const scope = yield Synks.SCOPE;\n  let count = 0;\n\n  // If pressed key is our target key then set to true\n  const downHandler = ({ key }) =\u003e {\n    if (key === 'ArrowUp') {\n      // Increment count state\n      count++;\n      // And update component\n      scope.next();\n    }\n  }\n\n  // Add event listeners\n  window.addEventListener('keydown', downHandler);\n\n  scope.onDestroy = () =\u003e {\n    // Remove event listeners on cleanup\n    window.removeEventListener('keydown', downHandler);\n  }\n\n  while (true) {\n    // Return count value back to component\n    yield count;\n  }\n}\n```\n\nOk this is how we create hook, but how do we use it?\n\n```jsx\nfunction* Counter() {\n  const count = yield countHook();\n\n  while (true) {\n    yield (\n      \u003ch1\u003e\n        {count.value}\n      \u003c/h1\u003e\n    );\n  }\n}\n```\n\nThis example available in [stackblitz.com/edit/vite-cdemev](https://stackblitz.com/edit/vite-cdemev?file=src/main.tsx)\n\nThis is it, now it's fully functional - locally scoped hook used in Counter component.\n\nNote that we use `count.value` instead of `count`, because this is what generator functions return. Also this helps to pass new value without calling `countHook` in while loop.\n\nBut that is not all!\n\nHooks can also use Context!\n\n```jsx\nfunction* countHook() {\n  const countContext = yield CountContext;\n\n  const downHandler = ({ key }) =\u003e {\n    if (key === 'ArrowUp') {\n      countContext.increment();\n      // We don't need to call `.next` here because\n      // context updates components itself\n    }\n  }\n\n  window.addEventListener('keydown', downHandler);\n}\n```\n\n## Architecture\n\nSynks renders everything to DOM asynchronously. This means every exported method except `h` returns Promise. It waits for every component in it's child tree to be ready and only then it renders it.\n\nThis is powerful when using async data fetching and syncing all dom tree together.\n\nIt doesn't do incremental rendering, except when you specifically allow it with suspended async generators.\n\nFor re-rendering - when updating parent component using `this.next` method, it's child components will ONLY be re-rendered again if props actually change.\n\nContext changes will only trigger update for components that are using that specific Context, not the whole context tree.\n\n## License\n\n[MIT](http://opensource.org/licenses/MIT)\n\nCopyright (c) 2020, [Marcis (Marcisbee) Bergmanis](https://twitter.com/marcisbee)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcisbee%2Fsynks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcisbee%2Fsynks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcisbee%2Fsynks/lists"}