Welcome to UIX! If you have used Vue before, this guide is for you.
UIX’s reactivity behaves very similarly to Vue’s. In fact, it is implemented on top of the same JavaScript mechanism that drives Vue 3. The syntax, however, is different, albeit based on familiar principles.
While JavaScript is popular with Vue developers, UIX builds solely on TypeScript. It is a type-safe superset of the JavaScript programming language, meaning that any code of the former is also valid code for the latter.
With Vue 3, you can start a hot-reloading live server with
npm run dev
With UIX, you use
uix -l
to start a live server instance with hot-reloading enabled.
When you visit your server’s main page, this is the first thing that is rendered. UIX has a similar concept. Here, we call it entrypoint. As UIX is a full-stack framework, backend/entrypoint.ts
will be executed when the server starts and frontend/entrypoint.tsx
will be rendered for every web browser that requests your website.
A Hello World Vue app would look like this:
<!-- src/App.vue --> <template> <h1>Hello World</h1> </template>
A Hello World UIX app would look like this:
// frontend/entrypoint.tsx export default <h1>Hello World</h1>;
JSX is a syntax extension for JavaScript. It is also used in React and allows developers to define DOM (HTML syntax) right inside JavaScript and TypeScript files. The special thing is that JSX expressions evaluate to actual HTMLElement instances that can be added to the DOM. It is therefore possible to assign HTML elements to variables like so:
const xyz = ( <div> <span>I will be used somewhere else later</span> </div> );
To create an HTML element with JSX, one root element will always be required (e.g. div
, as seen above). If, at any point in time, you wish to render literal text without markup in the browser, you can use bare JavaScript strings:
const xyz = "I will be used somewhere else later";
In Vue, you can use the {{ template }}
syntax to insert JavaScript expressions into HTML:
<!-- src/App.vue --> <script setup> const subject = "World"; </script> <template> <main>Hello, {{ subject }}!</main> </template>
With the JSX syntax that is used in UIX, you only use one curly brace on each side:
// frontend/entrypoint.tsx const subject = "World"; export default <main>Hello, {subject}!</main>;
In Vue, you can use the v-if
directive to conditionally render HTML:
<!-- src/App.vue --> <script setup> const sayHello = true; </script> <template> <main> <p v-if="sayHello">Hello, World!</p> <p v-else>Something Else!</p> </main> </template>
In UIX, which uses the JSX syntax, only JavaScript expressions are allowed. As if
and else
are statements and not expressions, the JavaScript Conditional Operator shall be used instead:
// frontend/entrypoint.tsx const sayHello = true; export default ( <main>{sayHello ? <p>Hello, World!</p> : <p>Something Else!</p>}</main> );
An alternative that is less redundant would be:
// frontend/entrypoint.tsx const sayHello = true; export default ( <main> <p>{sayHello ? "Hello, World!" : "Welcome to UIX!"}</p> </main> );
In Vue, you can use the v-for
directive to render elements based on an iterable (e.g. Array or Set):
<!-- src/App.vue --> <script setup> const data = [ { todo: "Learn UIX", done: true }, { todo: "Become a unyt.org member", done: true } ]; </script> <template> <main> <h1>Todo List</h1> <div v-for="item in data"> {{ item.todo }} - {{ item.done ? "Done" : "Outstanding" }} </div> </main> </template>
In UIX, you can use the standard JavaScript function map to convert an array of items into an array of HTML elements. UIX will automatically render arrays as multiple consecutive elements in the browser:
// frontend/entrypoint.tsx const data = [ { todo: "Learn UIX", done: true }, { todo: "Become a unyt.org member", done: true }, ]; export default ( <main> <h1>Todo List</h1> {data.map((item) => ( <div> {item.todo} - {item.done ? "Done" : "Outstanding"} </div> ))} </main> );
In the example above, each item of the data array gets mapped to a corresponding div.
Vue and UIX share some similarities when it comes to two-way data binding. Both are based on the underlying JavaScript Proxy System.
In Vue, you use ref()
to make values reactive. For complex data types on the other hand, you use reactive()
:
<!-- src/App.vue --> <script setup> import { ref, reactive } from "vue"; const counter = ref(0); const data = reactive([ { todo: ..., done: ...}, ... ]); </script>
In UIX, you use the all-in-one reactivity function $()
to make any kind of expression reactive:
// frontend/entrypoint.tsx const counter = $(0); const data = $([ { todo: ..., done: ...}, ... ]);
Certain elements like <input>
support bidirectional data binding.
With Vue, you can use the v-model
directive to bind a reactive variable bidirectionally:
<!-- src/App.vue --> <script setup> import { ref } from "vue"; const name = ref(""); </script> <template> <main> Your name: <input type="text" v-model="name"/> (Your name in uppercase: {{ name.toUpperCase() }}) </main> </template>
With UIX, you use HTML standard attribute names instead of v-model
attributes to bind data bidirectionally:
// frontend/entrypoint.tsx const name = $(""); export default ( <main> Your name: <input type="text" value={name} /> (Your name in uppercase: {val(name).toUpperCase()}) </main> );
In Vue, you use the computed()
function to make a variable have a certain value that is updated whenever its dependent data changes:
<!-- src/App.vue --> <script setup> import { ref, computed } from "vue"; const a = ref(39); const b = ref(3); const c = computed(() => a.value + b.value); </script>
In UIX, you use the always
function to declare any kind of reactively recomputable expression:
// frontend/entrypoint.tsx const a = $(39); const b = $(3); const c = always(() => a + b);
UIX automatically configures c
to be updated whenever a
or b
changes.
With Vue, you would commonly define components with props using the Vue Single File Component (SFC) Format:
<!-- src/components/ListItem.vue --> <script setup> const props = defineProps({ todo: String, done: boolean }); </script> <template> <div>{{ props.todo }} - {{ props.done ? "Done" : "Outstanding" }}</div> </template> <style> div { font-size: 0.9em; } </style>
With UIX, you use the already introduced TSX (TypeScript) file type to define execution logic, HTML content, and styles in a single file:
// frontend/components/ListItem.tsx import { Component } from "uix/components/Component.ts"; @template(props => <div> { props.todo } - { props.done ? "Done" : "Outstanding" } </div>; ) @style(css` div { font-size: 0.9em; } `) export class ListItem extends Component<{ todo: string, done: boolean }> { /* Place component logic, lifecycle hooks, instance members, methods etc. here. */ };
In UIX, components are plain TypeScript objects that inherit from the Component
base class whose parent is the native HTMElement
. They can optionally be decorated with @template
and @style
. Props are declared in the class declaration and can be used inside the class, the template, and the style.
In Vue, you import components and use the v-bind
(abbreviated as :
) directive to render components with props:
<!-- src/App.vue --> <script setup> import ListItem from "./components/ListItem.vue"; import { reactive } from "vue"; const example = reactive({ todo: "Learn UIX", done: true }); </script> <template> <ListItem v-bind:todo="example.todo" :done="example.done" /> </template>
In UIX, you import components and use curly braces to render them with props:
// frontend/entrypoint.tsx import { ListItem } from "./components/ListItem.tsx"; const example = $({ todo: "Learn UIX", done: true }); export default <ListItem todo={example.todo} done={example.done} />;
In Vue, you use dedicated event emissions and event listeners to communicate action from a component to its parent:
<!-- src/components/ListItem.vue --> <script setup> // ... const emit = defineEmits(["delete"]); </script> <template> <div> ... <button @click="emit('delete', props.todo, props.done)">Delete</button> </div> </template>
<!-- src/App.vue --> <script setup> import ListItem from "./components/ListItem.vue"; // ... function handleDeletion(todo, done) { // TODO: Process Deletion } </script> <template> <ListItem :todo="example.todo" :done="example.done" @delete="handleDeletion" /> </template>
In UIX, there is no dedicated event system. In fact, you can easily resemble Vue’s event functionality using a pure standardized JavaScript feature – callbacks:
// frontend/components/ListItem.tsx import { Component } from "uix/components/Component.ts"; @template(function (props) { return ( <div> ... <button onclick={props.onDelete(props.todo, props.done)}>Delete</button> </div> ); }) export class ListItem extends Component<{ todo: string; done: boolean; onDelete: (todo: string, done: boolean) => void; }> {}
Simply specify a component prop like onDelete
as a callback function. TypeScript allows you to strictly type the signature of this function. Then, use it in the parent environment:
// frontend/entrypoint.tsx import { ListItem } from "./components/ListItem.tsx"; // ... function handleDeletion(todo: string, done: boolean) { // TODO: Process Deletion } export default ( <ListItem todo={example.todo} done={example.done} onDelete={handleDeletion} /> );
For Vue, you use one of the external routing libraries. UIX supports Single and Multi-Page routing out of the box and features a versatile routing system. In its simplest form, it performs map-based routing:
// frontend/entrypoint.tsx export default { "/login": () => import("./login.tsx"), "/todos": () => import("./todos.tsx"), "/:userId/info": (_, { userId }) => ( <main> <h1>User Information</h1> For User ID {userId} </main> ), };
// frontend/login.tsx export default <main>...</main>;
Beyond that, UIX also supports file-based routing (although discouraged for larger projects) and dynamic route processing.
To use the integrated UIX backend rendering, with hydration enabled by default, you can use the backend/entrypoint.tsx
module to prerender your HTML on the server. The syntax remains the same.
To abstract away the necessity of manually transferring realtime data across devices using JSON, BSON, Remote Procedure Calls, WebSockets and similar technologies, UIX was designed to streamline this process deeply at its core. It employs a new realtime-data-centric communication protocol called DATEX – a technology developed and implemented by the same organization that created UIX.
Based on this underlying technology, UIX provides a feature called Cross Realm Imports.
// backend/data.ts const todo = $("Learn UIX");
// backend/data.ts export const todo = $("Learn UIX");
// frontend/entrypoint.tsx import { todo } from "backend/data.ts"; export default ( <main> Global ToDo: <input value={todo} /> </main> );
Now, the variable is synchronized across all website visitors in realtime with bidirectional data binding. You can also export functions from the backend and call them anywhere else. Note that function calls are always asynchronous since they are network transmissions after all. Make sure to use await
to get the return value of these calls.
UIX includes permanent data storage mechanisms that eliminate the need for an external database.
Create a reactive value
// backend/data.ts export const users = $({ example: [...] });
Annotate it with the eternal
label
// backend/data.ts export const users = eternal ?? $({ example: [...] });
Now, data is permanently stored across backend restarts. Execute uix --clear
to clear the stored records in your database.
Marking a pointer on the frontend with eternal
will persist it in the device’s localStorage
. As opposed to JSON, the underlying DATEX technology allows you to store any arbitrary value, including primitives, maps, class instances, callbacks, HTML elements, and more.
UIX and its adjacent projects have a lot more to offer. Check out our developer documentation to learn more about UIX and the future of web development.