Skip to content

Commit

Permalink
Write ApiReference
Browse files Browse the repository at this point in the history
  • Loading branch information
allevo committed Mar 28, 2024
1 parent 8a27bc5 commit e5ff4dc
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 26 deletions.
4 changes: 4 additions & 0 deletions packages/website/src/components/ContentWithToc.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
display: none;
padding-left: calc(1.5rem * .5);

.level-3 {
padding-left: 1.5rem;
}

ul {
list-style: none;
padding: 0;
Expand Down
5 changes: 4 additions & 1 deletion packages/website/src/components/ContentWithToc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ export interface Toc {
title: string;
slug: string;
type: "h2" | "h3";
level: number;
}

console.log(classes)

export async function ContentWithToc({
dom,
data,
Expand All @@ -19,7 +22,7 @@ export async function ContentWithToc({
const tocHTML = `<ul class="list-group">${toc
.map(
(t) =>
`<li class="list-group-item"><a class="" href="#${t.slug}">${t.title}</a></li>`,
`<li class="list-group-item ${classes['level-' + t.level]}"><a class="" href="#${t.slug}">${t.title}</a></li>`,
)
.join("")}</ul>`;

Expand Down
94 changes: 94 additions & 0 deletions packages/website/src/pages/ApiReference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
## start

This function starts `SeqFlow` and renders the component into the DOM element.

```ts
function start<T>(
el: HTMLElement,
fn: ComponentFn<T>,
option?: ChildOption<T>,
config?: Partial<StartParameters>,
): AbortController;
```

### Parameters

- `el: HTMLElement` - The DOM element where the component will be rendered.
- `fn: ComponentFn<T>` - The component function to be rendered.
- `option?: ChildOption<T>` - The initial state of the component.
- `config?: Partial<StartParameters>` - The configuration of the `SeqFlow` instance.
- `log: (l: Log) => void` - The log function to be used internally. No logs are printed if this function is not provided.
- `domains` - The object that let you to create the custom domains. Empty object by default.
- `navigationEventBus` - The event bus for the navigation events. An `EventTarget` instance is used by default.
- `config` - The application configuration object.

### Returns

- `AbortController` - The `AbortController` instance that can be used to stop the whole `SeqFlow` instance.

## ComponentFn

The component function is an asynchronous function that renders the component into the DOM element.

```ts
type ComponentFn<T = unknown> = (
param: ComponentParam<T>,
) => Promise<void>;
```

### Parameters

- `param: ComponentParam<T>` - The component parameter object.


## ComponentParam<T>

This interface is the parameter object that is passed to the component function.

### Fields

- `data: T` - The data object that is passed to the component. This is where application data is placed.
- `domains: Domains` - The object that contains the custom domains you created using `start` function.
- `signal: AbortSignal` - The `AbortSignal` instance that can be used to stop operation in the component. For instance you can use this signar in the `fetch` function to stop the request if in the meanwhile the component is unmounted.
- `router: Router` - The router object that contains the navigation methods.
- `dom: Dom` - The DOM object that contains the methods to render the component.
- `event: Event` - The event object that contains the methods to handle the events.

## Domains

This interface is the object that contains the custom domains you created using `start` function.
This interface is empty by default and you can add your custom domains.

## Router

This interface is the object that contains the navigation methods.

### Properties

- `navigate: (url: string) => void` - The method to navigate to the given URL.
- `segments: string[]` - The array of the segments of the current URL.
- `query: Map<string, string>` - A map that contains the parameters of the current URL.

## Dom

This interface is the object that contains the methods to render the component.

### Properties

- `render: (html: string) => void` - The method to render the HTML string into the DOM element.
- `querySelector<E = HTMLElement>(selector: string): E` - The method to query the DOM element by the given selector. This method is a wrapper of the `querySelector` method of the DOM element.
- `querySelectorAll<E extends Node = Node>(selector): NodeListOf<E>` - The method to query all the DOM elements by the given selector. This method is a wrapper of the `querySelectorAll` method of the DOM element.
- `child(id: string, fn: ComponentFn<unknown>): void` - The method to render the child component into the DOM element. The `id` is the unique identifier of the child component.
- `child<E>(id: string, fn: ComponentFn<E>, option: ChildOption<E>): void` - The method to render the child component into the DOM element with the initial state. The `id` is the unique identifier of the child component.

## Event

This interface is the object that contains the methods to handle the events.

### Properties

- `domainEvent<BEE extends typeof DomainEvent<unknown>>(b: BEE, ):AbortableAsyncGenerator<InstanceType<BEE>>` - The method to create an async generator that waits for the DOM event to be triggered.
- `domEvent<K extends keyof HTMLElementEventMap>( type: K, option?: Partial<DomEventOption>, ): AbortableAsyncGenerator<Event>;` - The method to create an async generator that waits for the DOM event to be triggered. A custom option can be passed to the method to customize the event listener.
- `navigationEvent(): AbortableAsyncGenerator<Event>;` - The method to create an async generator that waits for the navigation event to be triggered.
- `waitEvent<T extends Event[]>(...fns: { [I in keyof T]: AbortableAsyncGenerator<T[I]> } ): AsyncGenerator<T[number]>;` - The method combines multiple async generators into one. The method generates the events from the first generator that is triggered.
- ` dispatchDomainEvent<D, BE extends DomainEvent<D> = DomainEvent<D>>(event: BE);` - The method to dispatch the domain event to the event bus.
7 changes: 6 additions & 1 deletion packages/website/src/pages/ApiReference.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ComponentParam } from "seqflow-js";
import { ContentWithToc } from "../components/ContentWithToc";
import { html, toc } from "./ApiReference.md";

export async function ApiReference({ dom }: ComponentParam) {
dom.render("WIP");
dom.render(`<div id="api-reference"></div>`);
dom.child("api-reference", ContentWithToc, {
data: { toc, html, title: "Api Reference" },
});
}
29 changes: 15 additions & 14 deletions packages/website/src/pages/Why.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
## From where I started

As a developer, I worked with many technologies, from the backend to the frontend.
Meanwhile, I was familiarized with the backend technologies, started to work with the frontend ones, and was fascinated by the diversity of the frameworks and libraries we have to build our applications. Those differences are not always easy to manage and introduce some complexity. Virtual DOM, signals, and observers are promising technologies, but sometimes they are more challenging to understand and use.
While familiar with the backend technologies, I started working with the frontend ones. I was fascinated by the diversity of the frameworks and libraries we have to build our applications.

This complexity was highlighted when I (and my team) had to build web pages that the user could access only after a login.
I started using Angular and React, but when I tried to build something more complex, the frameworks and libraries introduced a lot of complexity and cognitive stress:
- While my code organization at the back-end follows a domain-driven design, my front-end code is structured differently. Consequently, the code is organized in a way that is only sometimes clear about the purpose of the code: no framework or library suggested anything on this topic.
- SSR, SSG, ISC, and other techniques are helpful. The main project I followed was related to a website under a login where the SEO is optional. Instead, the HTTP JSON Rest API was requested to be exposed, the same as the front-end uses. So, full-stack technologies aren't valuable for this use case.
- Node.js and similar technologies have become famous because the development team can use the same language on both sides: the team can share code between the front-end and back-end, like validation. Anyway, the portion of the shared code could be higher in terms of how the state management, components, and events are handled by the frameworks.

Moreover, I didn't use the Javascript/Typescript language at its best: I didn't use the latest features, like classes, async/await, async iterators, and others that simplify the code a lot. Instead, they are replaced with libraries like ngrx, redux, and others that introduce much boilerplate code and complexity.

That means that I had to study a lot of different libraries and frameworks to understand what I was doing. And this creates confusion and cognitive stress in me.
I have also tried different frameworks but didn't find someone to resolve all my dilemmas.

## What I was looking for

I appreciate the innovation and diversity of the frontend world, which brings me here: I want to look for a way to simplify my work, reduce the complexity, and use language features almost everywhere.
I appreciate the innovation and diversity of the frontend world, which brings me here: I want to look for a way to simplify my work, reduce the complexity, and use language features almost everywhere. I want to use a framework that helps me to build digital products, focused on simplifing the user experience improvement.

From this point, I started thinking about what I liked and didn't like about the front-end world.
The output of this thinking was the following list:
- The component-based architecture allows the UI to split into small, reusable, and independent pieces. It is also the way to build a design system.
- The class usage to define application logic is a way to organize the code and explain the application logic.
- The async/await usage is a way to handle the asynchronous code. In the past, we used callbacks, then promises, and now async/await. I want to use the latest features of the language.
- The structure of the application logic follows the team logic without putting any constraint on it. So, the class, function, and enum usages are possible.
- The async/await usage is a way to handle the asynchronous code. In the past, the front-end code used callbacks after promises, but now, all browsers can use the async/await language feature.
- The Typescript support is a way to reduce errors and improve code quality. Even if only some people like it, it is an excellent tool to enhance the code quality and let other developers understand the code and contribute to it.
- The simplicity of integrating into other frameworks and supporting multiple versions on the same page without strange tricks.

## The solution I thought

So, starting from here, I try to build something new, keeping in mind the following key concepts:
- Events over State Management
- Simplicity over Complexity
- Events over State Management
- Linearity over Complex Abstractions
- Explicitness over Implicitiveness

With this in mind, I started to write the `SeqFlow` framework. The following are some reasons for the design.
With this in mind, I started to write the `SeqFlow` framework. The following are some decisions I will take to resolve my dilemma, considering the previous ones.

### Render component

I started to think about a way to simplify the component rendering, and I thought about the following requirements:
- The component should be a function, not a class. This is because the class introduces a lot of boilerplate code, and it is not always clear what the purpose of the class is.
- The component should be able to process the asynchronous operations, like the data fetching, and the rendering should be updated when the data is available.
- The component read should be made from top to bottom without re-rendering the whole component when the state changes. If something changes, the component changes only the part that needs to be updated.
- The component read should be made from top to bottom without re-rendering the whole component when the state changes. If something changes, the code explicitly updates the rendered HTML.

From this list, I structured the component as an asynchronous function, where the developer can use the async/await syntax to handle the asynchronous operations.

Expand Down Expand Up @@ -80,7 +81,7 @@ This allows the developer to stop the `SeqFlow` instance and free the resources

### Event handlers

For historical reasons, the event handlers are defined as callbacks, which requires a different mindset to handle the asynchronous operations. The callbacks are not always easy to understand, they are not always easy to test, they are not always easy to maintain. Moreover, the callbacks are not always easy to compose with other operations, like the data fetching. For this reason, `SeqFlow` introduces a new way to handle the event handlers using the async iterators.
For historical reasons, the event handlers are defined as callbacks, which requires a different mindset to handle the asynchronous operations. The callbacks are not always easy to read, they are not always easy to test, and they are only sometimes easy to maintain. Moreover, callbacks are challenging to compose with other operations, such as data fetching. For this reason, `SeqFlow` introduces a new way to handle the event handlers using the async iterators.

```ts
import { ComponentParam } from "seqflow-js";
Expand All @@ -101,9 +102,9 @@ You can listen more events at the same time, see the [API reference](/api-refere

### Domains

The more the features are added, the more the complexity is introduced. To reduce the complexity, `SeqFlow` introduces the concept of domains. A domain is a way to group the components and the event handlers, and it is a way to reduce the complexity and the cognitive stress. The domain is a way to organize the code and the application logic.
The more the features are added, the more the complexity is introduced. To reduce the complexity, `SeqFlow` introduces the concept of domains. A domain is, shortly, a way to group the components and the event handlers, and it is a way to reduce the complexity and the cognitive stress. The domain is a way to organize the code and the application logic.

So, the application code is organized into folders, one for each domain. Each folder contains the components relative to that domain, and classes to handle the application logic.
So, the application code is organized into folders, one for each domain. Each folder contains the components relative to that domain and classes to handle the application logic.

See the <a target="_blank" href="https://github.com/allevo/seqflow-js/tree/main/examples/e-commerce">E-Commerce example</a> for more information about it.

Expand Down
21 changes: 12 additions & 9 deletions packages/website/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,18 @@ function loadMarkdownPlugin({ components }: { components: { tag: string, open: s

const toc: any[] = []
// add id to all h2 and h3
html = html.replace(/<h2>(.*)<\/h2>/g, (match, m) => {
const slug = m.toLowerCase().replace(/ /g, '-')
toc.push({ slug, title: m, type: 'h2' })
return match.replace(/<h2>/, `<h2 id="${slug}">`)
})
html = html.replace(/<h3>(.*)<\/h3>/g, (match, m) => {
const slug = m.toLowerCase().replace(/ /g, '-')
toc.push({ slug, title: m, type: 'h3' })
return match.replace(/<h3>/, `<h3 id="${slug}">`)
html = html.replace(/<h(\d)>/g, (match, m, index) => {
const aa = html.slice(index + 3).match(`</h${m}>`)
const title = html.slice(index + 4, aa.index + index + 3)

const last = toc[toc.length - 1]
let slug = title.toLowerCase().replace(/ /g, '-')
if (last && last.level < Number(m)) {
slug = last.slug + '-' + slug
}

toc.push({ slug, title, type: 'h' + m, level: Number(m) })
return `<h${m} id="${slug}">`
})

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/website/vite.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

declare module '*.md' {
const toc: { slug: string, title: string, type: 'h2' | 'h3' }[];
const toc: { slug: string, title: string, type: 'h2' | 'h3', level: number }[];
const html: string;

export { toc, html };
Expand Down

0 comments on commit e5ff4dc

Please sign in to comment.