Skip to content

Add package prepare step #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"no-var": ["error"],
"workspaces/no-relative-imports": "error",
"workspaces/require-dependency": "warn"
}
},
"ignorePatterns": ["**/dist/*"]
}
113 changes: 3 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,6 @@
# pipe
# Pipe

A Reactive DOM UI Library built on RxJS and TypeScript.

## What is Pipe?

Pipe is a declarative UI library, similar to React.js or Solid.js. This library seeks to provide a lightweight scripting layer on top of HTML/CSS, allowing developers to create simple and interactive web sites. In its current form, the library does not provides only utilities for dynamically rendering HTML into a document. It is not opinionated about state management, single page vs. multi-page applications, or styling. In theory, the library could be used as a drop-in replacement for React, though it is unclear how well that would scale. However, it would also be possible to create many pipe `roots`, as there is minimal overhead for creating a root, and scheduling is delegated to RxJS.

Pipe is built around observables, specifically the `RxJS` implementation of observables. Observables are reactive and functional in nature, which makes them ideal for programming declarative UI interfaces. Observables are passed as props to DOM elements, which are initialized immediately when `createElement` is called. The DOM element subscribes to each observable prop, and updates immediately when a value is received. There is some overhead here from RxJS to schedule these state updates. This is slightly different from a library like React, which inserts element creation and state updates into an event queue, and constructs/updates a Virtual DOM from these updates before committing them to the actual DOM. The latter approach may scale better with more complex components, avoiding potential issues like screen tearing with batched updates. The former approach is likely faster for less complex components, though.

Each component function is only called once. This allows us to avoid the need to memoize state updates in most situations. The only exception is when an observable passes a large table of props that need to be parsed separately. In this case, the `distinctUntilKeyChanged` operator is quite useful.

## Installation

```
npm install --save @hmallen99/pipe
```

## Usage

Creating a root:

```ts
const rootElement = document.querySelector<HTMLDivElement>('#root');

const root = createRoot(rootElement);

root.render(createElement(App, {}));
```

Creating a component:

```ts
const Counter: Component<Record<string, Observable<void>>> = () => {
const click$ = new Subject<void>();
const onclick = of(() => {
click$.next();
});
const textContent = click$.pipe(
scan((x) => x + 1, 0),
map((x) => String(x)),
);

return createElement('button', {
onclick,
textContent: concat(of('Click Me!'), textContent),
});
};

const App: Component<Record<string, Observable<void>>> = () => {
return createElement('div', {}, [createElement(Counter, {})]);
};
```

Child elements can be either static or dynamic. If a child component is never updated, it can be passed through a list of child elements, as shown above. If child components need to be removed or inserted, an observable can be passed instead of a static array. This allows for rendering lists of children or conditional children. A stable key must be passed with every child element in this configuration to allow for updating and removing the correct child element.

Conditional children:

```ts
const SwitchComponent: Component<{
bool$: Observable<boolean>;
}> = ({ bool$ }) => {
return createElement(
'div',
{},
bool$.pipe(
map((value) => [
'default',
value
? createElement('p', {
textContent: new BehaviorSubject('true'),
})
: createElement('p', {
textContent: new BehaviorSubject('false'),
}),
]),
),
);
};
```

List of children:

```ts
const TextList: Component<{
addChild$: Observable<[string, string]>;
}> = ({ addChild$ }) => {
return createElement(
'div',
{},
addChild$.pipe(
map(([key, value]) => [
key,
createElement('p', {
textContent: of(value),
}),
]),
),
);
};
```

RxJS observables are re-exported through the pipe library. To avoid importing multiple versions of RxJS, import RxJS observables and operators through `@hmallen99/pipe`

```ts
import { createElement, createRoot, Subject, of } from '@hmallen99/pipe';
```

For a more detailed exploration of usage, view the [example todo app](./apps/todo-app/src/TodoApp.ts).

## Planned work

Track bugs and new features on GitHub: https://github.com/hmallen99/pipe/issues
[Pipe library documentation](./packages/pipe/README.md)
[Example app](./apps/todo-app/README.md)
4 changes: 2 additions & 2 deletions apps/todo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "rsbuild build",
"dev": "rsbuild dev --open",
"format": "prettier --write .",
"lint": "eslint .",
"lint": "eslint src",
"preview": "rsbuild preview"
},
"devDependencies": {
Expand All @@ -19,6 +19,6 @@
"typescript-eslint": "^8.5.0"
},
"dependencies": {
"@hmallen99/pipe": "^1.0.1"
"@hmallen99/pipe": "^1.0.2"
}
}
Loading
Loading