Skip to content

Commit

Permalink
Merge pull request #125 from unyt-org/feat-editable-templates
Browse files Browse the repository at this point in the history
Docs updates / basic editable templates
  • Loading branch information
jonasstrehle authored Apr 24, 2024
2 parents 64024d0 + 09b285d commit 01470ad
Show file tree
Hide file tree
Showing 20 changed files with 366 additions and 61 deletions.
2 changes: 1 addition & 1 deletion auto-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ESCAPE_SEQUENCES } from "datex-core-legacy/utils/logger.ts";
import { Path } from "./src/utils/path.ts";

export async function handleAutoUpdate(baseLibPath: Path, name: string) {
const { renderMarkdown } = await import('https://deno.land/x/charmd/mod.ts');
const { renderMarkdown } = await import("https://deno.land/x/charmd@v0.0.2/mod.ts");

baseLibPath = baseLibPath.asDir()
// console.log("Finding updates for " + name + " (" + baseLibPath + ")")
Expand Down
47 changes: 27 additions & 20 deletions docs/manual/01 Getting Started.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# Getting Started with UIX

UIX is an open-source full-stack framework for developing reactive web apps with *restorable and shared state*.
UIX apps run on a [Deno](https://docs.deno.com/runtime/manual) backend and use state-of-the-art web technologies.
## What is UIX?

The [DATEX JavaScript Library](https://docs.unyt.org/manual/datex/introduction) acts as the backbone of UIX, providing useful functionality such as *reactivity and cross-device data exchange*.
In contrast to frameworks like React, UIX provides *direct wiring* to the DOM for reactivity and does not need a virtual DOM.
UIX is a state-of-the-art TypeScript framework for developing full-stack web applications.
With UIX, you can write frontend and backend code in a single [Deno](https://docs.deno.com/runtime/manual) project.
UIX abstracts away the complexity of communicating between servers and clients - there
is no need to think about APIs, serialization, or data storage.

**Our core principles**
* Full compatibility with web standards
* Full compatibility with [DATEX](https://github.com/unyt-org/datex-specification) and unyt.org Supranet principles
* Both backend and frontend code is written as ES6 TypeScript modules
* No JavaScript bundlers
The [DATEX JavaScript Library](https://docs.unyt.org/manual/datex/introduction) acts as the backbone of UIX, providing useful functionality such as *reactivity, restorable state and cross-device data exchange*.

UIX works out of the box with TypeScript and JSX and does not require any additional tooling or build steps.

UIX encourages the use of standard web APIs wherever possible, and provides a simple
and intuitive abstraction layer for more advanced features.

> [!NOTE]
> The [UIX Guide](./17%20Guide.md) gives a comprehensive overview for developers who are new to UIX.

## Main Features

**Main features**
* [Cross-network reactivity](02%20Cross-Realm%20Imports.md#Reactivity)
* [Server side rendering with partial hydration](07%20Rendering%20Methods.md)
* [Hybrid backend/frontend routing](05%20Entrypoints%20and%20Routing.md)
Expand All @@ -30,13 +37,10 @@ This is why UIX ships with integrated features such as:
* [Automated deployment](./13%20Deployment.md)
* [Testing library](https://github.com/unyt-org/unyt-tests/)

### CLI Installation
## Installation

To install UIX, you first need to install [Deno](https://docs.deno.com/runtime/manual/getting_started/installation).

> [!WARNING]
> UIX is only supported for Deno versions > 1.40.0
<!-- #### Linux / MacOS
```bash
Expand All @@ -56,17 +60,20 @@ $ brew install uix
Now, you can install UIX with `deno install`:

```bash
$ deno install --import-map https://cdn.unyt.org/uix/importmap.json -Aq -n uix https://cdn.unyt.org/uix/run.ts
deno install --import-map https://cdn.unyt.org/uix/importmap.json -Aq -n uix https://cdn.unyt.org/uix/run.ts
```

> [!WARNING]
> UIX is only supported for Deno versions > 1.40.0
## Creating a new UIX project

You can create a new UIX project by running
```bash
$ uix --init
uix --init
```

This creates a new base project (https://github.com/unyt-org/uix-base-project.git) in the current directory
This creates a new [base project](https://github.com/unyt-org/uix-base-project.git) in the current directory
and starts the app locally.

> [!NOTE]
Expand All @@ -79,7 +86,7 @@ To run your UIX app, make sure the [app.dx](./08%20Configuration.md#the-app-dx-f
Execute the `uix` command in the root directory of your application (where the `app.dx` is located) to initialize and run the project.

```bash
$ uix
uix
```

You can pass the following args to the UIX command line utility:
Expand Down Expand Up @@ -107,7 +114,7 @@ You can pass the following args to the UIX command line utility:

To run your UIX project without installing the UIX CLI, you can alternatively run the following command in the project root directory:
```bash
$ deno run -A --import-map https://cdn.unyt.org/importmap.json https://cdn.unyt.org/uix/run.ts
deno run -A --import-map https://cdn.unyt.org/importmap.json https://cdn.unyt.org/uix/run.ts
```

## Architecture of a UIX Project
Expand Down Expand Up @@ -147,7 +154,7 @@ interface UIX {
cacheDir: Path; // URL pointing to the local UIX cache directory
context: "backend"|"frontend"; // current context in which the process is running
language: string; // language ("de" | "en" | ...)
version: string; // UIX version ("beta" | "1.0.0" | ...)
version: string; // UIX version ("beta" | "0.2.0" | ...)
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/manual/02 Cross-Realm Imports.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Cross-Realm Imports

Modules in the `frontend` directory of a UIX app can import values from `backend` modules, as if they are running on the same device and within the same process.
This is accomplished with DATEX exchange between the frontend and backend endpoints.
Modules in the `frontend` directory of a UIX app can import exported values from `backend` modules, as if they are running on the same device and within the same process.
Under the hood, this is achieved with DATEX messaging between the frontend and backend endpoints.

**Cross-Realm Import Example**:

Expand Down Expand Up @@ -39,7 +39,7 @@ console.log(await getData()); // [1,2,3]

Modules from the common directory can be imported from both the backend and frontend.

This is useful for definining components that can be rendered by the backend or frontend, or for utility functions or libraries that are used on the backend and frontend.
This is useful for definining components that can be rendered by the backend or frontend, or for utility functions and libraries that are used on the backend and frontend.

> [!NOTE]
> Common modules allow the usage of the *same source code* for the backend and frontend, but they do not share a state between the backend and frontend endpoints: Every module is initialized individually on each endpoint.
Expand Down
10 changes: 5 additions & 5 deletions docs/manual/03 JSX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JSX

UIX supports JSX syntax for creating HTML and SVG elements.
UIX supports [JSX](https://facebook.github.io/jsx/) syntax for creating HTML and SVG elements.

## Creating native DOM elements

Expand Down Expand Up @@ -331,9 +331,9 @@ const comp = <MyComponent style="color:green" text="text content"/>
```


## Using the `HTML` utility function instead of JSX
## Using `HTML` template strings instead of JSX

As an alternative to JSX, you can also use the `HTML` function which provides exactly the same functionality as JSX with JavaScript template strings.
As an alternative to JSX, you can also use the `HTML` template string function which provides exactly the same functionality as JSX:

JSX:
```tsx
Expand All @@ -347,7 +347,7 @@ const div =
HTML:
```tsx
const count: Datex.Pointer<number> = $$(0);
const div = HTML`
const div = HTML `
<div>
<p>Count: ${count}</p>
</div>` as HTMLDivElement
Expand Down Expand Up @@ -385,4 +385,4 @@ const anchor = <a href="/link">Link</a> as HTMLAnchorElement
```
```tsx
const anchor = HTML `<a href="/link">Link</a>` as HTMLAnchorElement
```
```
2 changes: 1 addition & 1 deletion docs/manual/08 Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ UIX apps can be run in different **stages**. The names of the stages are not pre

To run a UIX app in a specific stage, use the `--stage` options:
```bash
$ uix --stage production
uix --stage production
```

Per default, running a UIX app in a different stage does not have any noticable effect.
Expand Down
21 changes: 18 additions & 3 deletions docs/manual/11 Style and Themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,25 @@ but you can override the current mode:
UIX.Theme.setMode('dark');
```

### Observing mode changes
### Observing theme and mode changes

Changes between dark and light mode can be handled with `UIX.Theme.onModeChange`:
For some use cases, it might be useful to change content or styling depending on the current theme or mode.

The `theme` and `mode` properties of `UIX.Theme` are bound to reactive pointers and can be used in combination
with `effect` and `always` to react to changes:

```ts
UIX.Theme.onModeChange(mode => console.log("mode changed to", mode);)
effect(() => console.log(`Mode changed to ${UIX.Theme.mode}`));
```

```tsx
<div>{
always(() =>
UIX.Theme.mode == "dark" ?
"Dark mode" :
"Light mode"
)
}</div>
```

Alternatively, you can directly access the underlying pointers with `UIX.Theme.$.theme` / `UIX.Theme.$.mode`.
79 changes: 69 additions & 10 deletions docs/manual/17 Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
This guide conveys some important concepts and paradigms for developing applications with UIX/DATEX.
We recommend that you read this guide before starting to develop a UIX application to fully grasp the concepts and possibilities of the framework.


## Storage

In UIX, you normally don't need to think a lot about how and where to store data.
UIX provides an abstraction layer that essentially treats in-memory data and persistent data the same way.
In UIX, you don't need to think a lot about how and where to store data.
UIX provides an abstraction layer that essentially treats persistent data the same way as in-memory data.

The most important point to take away is that you don't need to think about a database architecture or serialization strategy
when building a UIX app - with eternal pointers, this is all been taken care of by UIX.
Expand All @@ -25,7 +24,7 @@ interface UserData {
export const users = new Set<UserData>()
```

Now make the module containing the `users` Set eternal by using an `eternal.ts` file extension:
Now make the module containing the `users` Set eternal by using the `eternal.ts` file extension:
```tsx
// file: data.eternal.ts
// The code stays the same:
Expand All @@ -36,9 +35,9 @@ interface UserData {
export const users = new Set<UserData>()
```

The exported `users` Set is now stored persistently and the current state is still available after a restart of the application.
The exported `users` set is now stored persistently and the current state is still available after an application restart.

This works out of the box without any special functions or data types. For larger amounts of data, you can optimize this
This works out of the box without any special functions or data types. For larger data sets, you can optimize this
by using a special storage collection instead of a native `Set`:
```tsx
// data.eternal.ts
Expand All @@ -49,25 +48,85 @@ interface UserData {
export const users = new StorageSet<UserData>()
```

A `StorageSet` provides the same methods and properties as a normal `Set`, but it works asynchronously and saves a lot of memory by lazily loading
data into memory when required.
A `StorageSet` has the same methods and properties as a normal `Set`, but it works asynchronously and saves a lot of memory by lazily loading
data from storage into memory when required.

### Storage locations

Under the hood, UIX can use multiple strategies for storing eternal data, such as in a key-value store, an SQL database, or local storage in the browser.

On the backend, eternal data is stored in a simple key-value database per default.
As an alternative, you can use an SQL database, which is more suitable for larger data sets where you need to query data.
Switching to SQL storage *does not require any changes in your application code* - it just changes the underlying storage mechanism.

On the frontend, eternal data is stored in the browser's local storage and IndexedDB.

## Networking

UIX also creates an intuitive abstraction around the network layer and DATEX communication.
UIX creates an intuitive abstraction around the network layer and [DATEX](https://docs.unyt.org/manual/datex/introduction) communication.
You don't need to think about how to send data from the backend to the frontend or between browser clients.
Instead, you can just call a JavaScript function - no need for API architectures and REST. In UIX, your exported classes and functions *are* the API.

You want to get the age of a user from the backend?
Just call a function that returns the age of a user:

```tsx
// backend/age.ts
const users = new Map<string, {age: number}>();

export function getAgeOfUser(userName: string) {
return users.get(userName)?.age
}
```

And in the frontend, you can call this function as if it was a local function:

```tsx
// frontend/age.ts
import { getAgeOfUser } from "backend/age.ts"
console.log(await getAgeOfUser("1234"))
```

Although you can retrieve individual object properties this way, the preferred
way in UIX is to just share the whole user object and read the required properties directly
on the frontend:

```tsx
// backend/age.ts
const users = new Map<string, {age: number}>();

export function getUser(userName: string) {
return users.get(userName)
}
```

```tsx
// frontend/age.ts
import { getUser from "backend/age.ts"

const userA = await getUser("1234");
console.log(userA.age); // get age
userA.age = 42 // set age (automatically synced across the network)
```


## Reactivity

In UIX, reactive values are called pointers.
Pointers can contain any kind of JavaScript value, including strings, numbers, objects, arrays, and functions.
DOM elements can also be bound to pointers, making them reactive.

When creating a DOM element with JSX, it is automatically bound to a pointer.

```tsx
const counter = $$(0); // create a reactive pointer with initial value 0
const counterDisplay = <div>{counter}</div>; // bind the pointer to a DOM element
document.body.appendChild(counterDisplay); // append the element to the DOM
counter.val++; // increment the pointer value - updates the DOM element
```
Reactivity in UIX works cross-network per default.
You can share reactive values (pointers) with other endpoints.
You can share and synchronize pointers with other endpoints.
## Forms
Expand Down
6 changes: 5 additions & 1 deletion src/app/frontend-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,10 @@ if (!window.location.origin.endsWith(".unyt.app")) {

// convert content to valid HTML string
if (content instanceof Element || content instanceof DocumentFragment) {
const requiredPointers = new Set<Node>()
const requiredPointers = new Set<Node>();

context = context instanceof Function ? context() : context;

const html = getOuterHTML(
content as Element,
{
Expand All @@ -868,6 +871,7 @@ if (!window.location.origin.endsWith(".unyt.app")) {
injectStandaloneComponents:render_method!=RenderMethod.STATIC,
allowIgnoreDatexFunctions:(render_method==RenderMethod.HYBRID||render_method==RenderMethod.PREVIEW),
lang,
editMode: !!context?.getSessionFlag("editMode"),
requiredPointers
}
);
Expand Down
Loading

0 comments on commit 01470ad

Please sign in to comment.