https://github.com/marcisbee/synks
🐉 Synks is a tiny javascript view renderer for generators as components and hooks
https://github.com/marcisbee/synks
components dom framework generators synks ui view
Last synced: 2 months ago
JSON representation
🐉 Synks is a tiny javascript view renderer for generators as components and hooks
- Host: GitHub
- URL: https://github.com/marcisbee/synks
- Owner: Marcisbee
- License: mit
- Created: 2020-04-18T10:57:07.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2023-04-05T09:16:30.000Z (about 2 years ago)
- Last Synced: 2025-04-10T06:50:31.655Z (2 months ago)
- Topics: components, dom, framework, generators, synks, ui, view
- Language: TypeScript
- Homepage:
- Size: 689 KB
- Stars: 7
- Watchers: 2
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
#
**Synks** is a tiny javascript view renderer, that is built async first components.
It uses JSX/hyperscript for it's templating.
Functions, promises and generators for component composition. And classes for contexts.[](https://www.npmjs.com/package/synks)
[](https://www.npmjs.com/package/synks)
[](https://bundlephobia.com/result?p=synks)
[](https://discord.gg/a62gfaDW2e)## Installation
To install the stable version:
```
npm install --save synks
```This assumes you are using [npm](https://www.npmjs.com/package/synks) as your package manager.
If you're not, you can [access these files on unpkg](https://unpkg.com/synks/dist/), download them, or point your package manager to them.
## Documentation
I'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.
#### Hello World example
For simple output we can use simple functional component.
```jsx
function Hello({ what }) {
returnHello {what}
;
}Synks.mount(, document.body);
```This example will mount h1 to body like so `
Hello World
`#### Counter example
For this we'll need to use generators as they can have state inside them.
```jsx
function *Counter() {
let count = 0;const increment = () => {
count++;
this.next();
}while(true) {
yield (
{count}
)
}
}
```#### Data fetch example
To handle async data fetching, we can use `async` functional component for this.
```jsx
async function Movies() {
const movieList = await api.getMovieList();return (
- {movie.title}
{movieList.map((movie) => (
))}
);
}
```
_NOTE:_ This will halt all rendering of it's sibling components.
#### Suspense example
This will allow sibling components to render even when all of the children's here are not yet ready.
```jsx
function Loading() {
return
}
async function *Suspense({ fallback, children }) {
// Here we are saying when fallback is mounted, go to next yield.
this.onMount = this.next;
while (true) {
yield fallback;
yield (
);
}
}
```
This will not stop DOM rendering at async components.
#### Context example
And finally we can use context to sync multiple components with ony state.
For this we need to extend `Synks.Context` class to build our own context.
```jsx
class CountContext extends Synks.Context {
count = 0;
increment() {
this.count += 1;
}
}
```
`count` is a state variable that defaults to `0` and `increment` is a method that increments that count variable. Easy.
Now let's use this context.
```jsx
Synks.mount(
);
```
No extra steps or requirements here, just wrap your context around child components that will use this context.
Ok, 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`.
```jsx
function *StateCounter() {
const countContext = yield CountContext;
while (true) {
yield (
{countContext.count}
)
}
}
```
Ok so when you `yield` a context it automatically returns corresponding context.
_NOTE_: Do not destruct `countContext` as it will be transformed to simple value and not be updatable.
Here's a more in depth example of Contexts: [stackblitz.com/edit/vite-6agmqe](https://stackblitz.com/edit/vite-6agmqe?file=src/main.tsx)
#### Code reuse (mixins vs hooks story)
Let'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.
Ok, 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.
So 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.
I currently call them hooks internally as they work more like hooks.
It's just a generator function with ability to get parent components scope.
Let's create our first hook, that will listen to `keypressed` events and increment value accordingly.
```jsx
function* countHook() {
/**
* First of all lets get scope of parent component.
* This will allow us to call `scope.next()` to update
* component, just like we do in components.
*/
const scope = yield Synks.SCOPE;
let count = 0;
// If pressed key is our target key then set to true
const downHandler = ({ key }) => {
if (key === 'ArrowUp') {
// Increment count state
count++;
// And update component
scope.next();
}
}
// Add event listeners
window.addEventListener('keydown', downHandler);
scope.onDestroy = () => {
// Remove event listeners on cleanup
window.removeEventListener('keydown', downHandler);
}
while (true) {
// Return count value back to component
yield count;
}
}
```
Ok this is how we create hook, but how do we use it?
```jsx
function* Counter() {
const count = yield countHook();
while (true) {
yield (
{count.value}
);
}
}
```
This example available in [stackblitz.com/edit/vite-cdemev](https://stackblitz.com/edit/vite-cdemev?file=src/main.tsx)
This is it, now it's fully functional - locally scoped hook used in Counter component.
Note 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.
But that is not all!
Hooks can also use Context!
```jsx
function* countHook() {
const countContext = yield CountContext;
const downHandler = ({ key }) => {
if (key === 'ArrowUp') {
countContext.increment();
// We don't need to call `.next` here because
// context updates components itself
}
}
window.addEventListener('keydown', downHandler);
}
```
## Architecture
Synks 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.
This is powerful when using async data fetching and syncing all dom tree together.
It doesn't do incremental rendering, except when you specifically allow it with suspended async generators.
For re-rendering - when updating parent component using `this.next` method, it's child components will ONLY be re-rendered again if props actually change.
Context changes will only trigger update for components that are using that specific Context, not the whole context tree.
## License
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2020, [Marcis (Marcisbee) Bergmanis](https://twitter.com/marcisbee)