Skip to content

Commit

Permalink
Add NPM Release Workflow (#13)
Browse files Browse the repository at this point in the history
* Update package scope

* Add release workflow

* Update version

* Add README

* Update README
  • Loading branch information
hmallen99 authored Oct 12, 2024
1 parent 1b64b62 commit 9fdd911
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 12 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Publish package to npm

on:
release:
types: [published]

jobs:
teat-and-release:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Check Lint
run: npm run lint

- name: Check Style
run: npm run style

- name: Run Tests
run: npm test -- --coverage --coverageReporters lcov

- name: Publish to npm
run: |
cd packages/pipe
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,113 @@
# 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
4 changes: 3 additions & 1 deletion apps/todo-app/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Rsbuild Project
# Example TODO App

A simple todo list application built with pipe.

## Setup

Expand Down
2 changes: 1 addition & 1 deletion apps/todo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
"typescript-eslint": "^8.5.0"
},
"dependencies": {
"@pipe/pipe": "^0.0.1"
"@hmallen99/pipe": "^0.0.1"
}
}
2 changes: 1 addition & 1 deletion apps/todo-app/src/TodoApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
PipeNode,
scan,
Subject,
} from '@pipe/pipe';
} from '@hmallen99/pipe';

export const TodoApp: Component<Record<string, Observable<void>>> = () => {
const clickAdd$ = new Subject<string>();
Expand Down
2 changes: 1 addition & 1 deletion apps/todo-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createElement, createRoot } from '@pipe/pipe';
import { createElement, createRoot } from '@hmallen99/pipe';

import './index.css';
import { TodoApp } from './TodoApp';
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pipe",
"version": "0.0.1",
"version": "1.0.0",
"description": "JavaScript UI Library",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/pipe/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@pipe/pipe",
"name": "@hmallen99/pipe",
"version": "0.0.1",
"description": "Pipe Library",
"main": "dist/src/index.js",
Expand Down

0 comments on commit 9fdd911

Please sign in to comment.