Repository: Rezact/Rezact
Pull request #16 (2023-09-29 22:20:36)

Compiler refactor and Compiler Explanation
- refactor compiler and framework function names
- bug fix for weird side effect with form input attribute handlers
- add escape hatch examples and starter test (not tested yet)
- add Compiler Explanation readme


Patch:
diff --git a/COMPILER.md b/COMPILER.md
new file mode 100644
index 0000000..77110c6
--- /dev/null
+++ b/COMPILER.md
@@ -0,0 +1,256 @@
+# Understanding the Rezact Compiler Plugin
+
+## Introduction
+
+The Rezact Compiler Plugin is a transformation tool designed to parse and transform source code written using the Rezact library. It is implemented as a Vite plugin and primarily focuses on handling state and reactivity in web development contexts.
+
+## Workflow
+
+1. **Parsing Source Code:**
+   The plugin uses the Acorn library to parse the source code into an Abstract Syntax Tree (AST).
+2. **Walking the AST:**
+   The plugin walks through the AST using `acorn-walk` to identify nodes that require transformation.
+3. **Applying Transformations:**
+   Based on the identified nodes, the plugin applies transformations using `magic-string` to manipulate the source code strings.
+
+## Example 1 - Signals & Effects
+
+### Source Code
+
+```javascript
+let $name = "jesen";
+let $width = 10;
+let $height = 20;
+
+let $area = $width * $height;
+
+$width = 20;
+$height = 40;
+```
+
+### Transformed Output
+
+```javascript
+import { Signal, effect } from "rezact/signals";
+
+let $name = new Signal("jesen");
+let $width = new Signal(10);
+let $height = new Signal(20);
+
+let $area = effect(
+  ([$width, $height]) => $width.get() * $height.get(),
+  [$width, $height]
+);
+
+$width.set(20);
+$height.set(40);
+```
+
+## Example 2 - MapSignals
+
+### Source Code
+
+```javascript
+export let $items = [];
+
+export function addItem() {
+  $items.push({
+    $price: "2",
+    $qty: "1",
+  });
+}
+```
+
+### Transformed Output
+
+```javascript
+export let $items = new MapSignal([]);
+
+export function addItem() {
+  $items.push({
+    $price: new Signal("2"),
+    $qty: new Signal("1"),
+  });
+}
+```
+
+## Example 3 - Full Component (JSX output retransformed to be readable)
+
+### Source Code
+
+```javascript
+export function Page() {
+  let $test = ["Jack", "Jill", "John", "Jane"];
+  return (
+    <>
+      <h1>Simple String List</h1>
+
+      {$test.map(($name) => (
+        <div dataVal={$name}>
+          <input value={$name} />
+        </div>
+      ))}
+
+      {$test.map(($name) => (
+        <div>{$name}</div>
+      ))}
+    </>
+  );
+}
+```
+
+### Transformed Output
+
+```javascript
+import { MapSignal, mapEffect } from "rezact/mapState";
+import { useInputs, xCreateElement, xFragment } from "rezact";
+useInputs();
+
+export function Page() {
+  let $test = new MapSignal(["Jack", "Jill", "John", "Jane"]);
+  let $mdup8zy7a7f = mapEffect(([$test]) => $test.Map((item) => item), [$test]);
+  let $myvzag0e2rn = mapEffect(([$test]) => $test.Map((item) => item), [$test]);
+
+  return (
+    <>
+      <h1>Simple String List</h1>
+
+      {mapEffect(
+        ([$mdup8zy7a7f]) =>
+          $mdup8zy7a7f.Map(($name) => (
+            <div dataVal={$name}>
+              <input value={$name} />
+            </div>
+          )),
+        [$mdup8zy7a7f]
+      )}
+
+      {mapEffect(
+        ([$myvzag0e2rn]) => $myvzag0e2rn.Map(($name) => <div>{$name}</div>),
+        [$myvzag0e2rn]
+      )}
+    </>
+  );
+}
+```
+
+### Why Two Separate mapEffects?
+
+In the provided transformed output, there are two `mapEffect` instances over the original `$test` `MapSignal` because there are two separate `.map` calls in the returned JSX. Each `.map` call in the JSX corresponds to a `mapEffect` in the transformed output.
+
+### Source Code
+
+In the JSX, there are two separate `.map` calls on the `$test` variable:
+
+```javascript
+{
+  $test.map(($name) => (
+    <div dataVal={$name}>
+      <input value={$name} />
+    </div>
+  ));
+}
+
+{
+  $test.map(($name) => <div>{$name}</div>);
+}
+```
+
+### Transformed Output
+
+Each of these `.map` calls is transformed into a separate `mapEffect` in the transformed output:
+
+```javascript
+let $mdup8zy7a7f = mapEffect(([$test]) => $test.Map((item) => item), [$test]);
+let $myvzag0e2rn = mapEffect(([$test]) => $test.Map((item) => item), [$test]);
+```
+
+Each `mapEffect` is responsible for creating a separate reactive mapping over the `$test` `MapSignal`. The first `mapEffect`, `$mdup8zy7a7f`, corresponds to the first `.map` call in the source code, and the second `mapEffect`, `$myvzag0e2rn`, corresponds to the second `.map` call in the source code.
+
+This separation allows each mapping to be independently reactive to changes in the `$test` `MapSignal`, ensuring that the transformations and renderings inside each `mapEffect` are isolated from each other and only re-compute when necessary, based on changes to their respective dependencies.
+
+In summary, having two separate `mapEffect` instances for two `.map` calls allows for optimized, independent reactivity and rendering for each mapped section of the component.
+
+Absolutely, here’s a refined version that emphasizes the enhanced developer experience:
+
+---
+
+### Enhanced Reactivity in Arrays/Maps
+
+Rezact employs a meticulous and innovative approach to infuse reactivity within each element of an array, even for arrays of primitive types like strings. This approach not only optimizes performance but also significantly elevates the developer experience by automating the process of making each item reactive, freeing developers from the manual burden. Below is a detailed explanation of how Rezact achieves this:
+
+#### 1. **Automated Individual Item Wrapping**
+
+Rezact automatically transforms each item in the array into a reactive entity. Every element in the array is converted into a reactive construct, allowing developers to focus on logic and functionality rather than the intricacies of state management.
+
+#### 2. **Fine-Grained Dependency Tracking**
+
+Rezact tracks the dependencies between each specific item and the computations or effects that rely on it. This ensures that only the components that depend on the changed item are updated, avoiding unnecessary recalculations and re-renders, and optimizing performance.
+
+#### 3. **Accessors and Mutators for Precise Observation**
+
+Rezact employs accessors and mutators (getters and setters) for each reactive item, this allows for the observation and propagation of changes to that item. This enables developers to work with a responsive and intuitive system that accurately reflects state changes in the UI.
+
+#### 4. **Optimized and Selective Reactivity**
+
+By wrapping each item individually, Rezact ensures selective and efficient reactivity, updating only the components affected by a change in a specific item and preventing unnecessary updates and computations for the unaffected parts of the UI.
+
+#### 5. **Simplified Reactive Arrays**
+
+Rezact’s method allows even simple arrays of strings to exhibit advanced reactivity. Developers can work with reactive entities capable of independently triggering UI updates when modified, without having to manually manage the reactivity of each item, simplifying the development process and reducing the likelihood of errors.
+
+---
+
+## Conclusion
+
+The Rezact Compiler Plugin is a meticulous tool designed to transform source code written using the Rezact library. It parses the source code into an AST, walks through the AST to identify nodes requiring transformation, and applies the necessary transformations using `magic-string`. It transforms variables prefixed with `$` to their corresponding implementations in the Rezact library. It analyzes the value being assigned to the `$` prefixed variable to decide what type of implementation should be used and whether or not there are any signals the declared signal depends on.
+
+## Pulling the escape hatch (leak the abstraction)
+
+If you really want to get low level and hands on with the frameworks primitives you can import the functions and classes yourself.
+
+```javascript
+import { Signal, effect } from "rezact/signals";
+import { MapSignal, mapEffect } from "rezact/mapState";
+
+export function TestManual() {
+  let name = new Signal("jesen");
+  let width = new Signal(10);
+  let height = new Signal(20);
+
+  let area = effect(
+    ([width, height]) => width.get() * height.get(),
+    [width, height]
+  );
+
+  let test = new MapSignal(["Jack", "Jill", "John", "Jane"]);
+  let test1 = mapEffect(([test]) => test.Map((item) => item), [test]);
+  let test2 = mapEffect(([test]) => test.Map((item) => item), [test]);
+
+  return (
+    <div>
+      <div>Hallo, {name}</div>
+      <div>width: {width}</div>
+      <div>height: {height}</div>
+      <div>area: {area}</div>
+      <button onClick={() => width.set(width.get() + 30)}>Update Width</button>
+
+      <h1>Simple String List</h1>
+
+      {mapEffect(
+        ([test1Arr]) =>
+          test1Arr.Map((name) => (
+            <div dataVal={name}>
+              <input value={name} />
+            </div>
+          )),
+        [test1]
+      )}
+      {mapEffect(
+        ([test2Arr]) => test2Arr.Map((name) => <div>{name}</div>),
+        [test2]
+      )}
+    </div>
+  );
+}
+```
diff --git a/README.md b/README.md
index fbe1ef3..97b5909 100644
--- a/README.md
+++ b/README.md
@@ -363,6 +363,12 @@ Rezact's encouragement of uncontrolled components offers a refreshing take on fo
 
 ---
 
+# Understanding the Rezact Compiler Plugin
+
+For a more in depth look at how it all works see here.
+
+---
+
 ## More Examples
 
 There are lots of examples in the `src/examples` folder in this repo: https://github.com/Rezact/Rezact/tree/main/src/examples
diff --git a/package.json b/package.json
index 7c4f638..21191ae 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@rezact/rezact",
-  "version": "1.0.15-beta.14",
+  "version": "1.0.15-beta.15",
   "type": "module",
   "description": "Intuitive Reactivity, Simplified State. Embrace a modern UI framework that encourages direct data mutations, offers module-level reactivity, and simplifies component design. With Rezact, you get the power of reactivity without the boilerplate. Dive into a seamless development experience tailored for today's web.",
   "main": "index.cjs",
diff --git a/src/components/appRouter.ts b/src/components/appRouter.ts
index c0a77cf..2b9b91f 100644
--- a/src/components/appRouter.ts
+++ b/src/components/appRouter.ts
@@ -57,6 +57,11 @@ router.addRoute(
   () => import("src/examples/Forms/ControlledForms")
 );
 
+router.addRoute(
+  "/escape-hatches",
+  () => import("src/examples/EscapeHatches/EscapeHatches")
+);
+
 router.addRoute("/todos", () => import("src/examples/Todo/Todo"));
 
 router.addRoute("/list-redraw", () => import("src/examples/ListReRender/App"));
diff --git a/src/examples/BenchmarkRezact/Benchmark.tsx b/src/examples/BenchmarkRezact/Benchmark.tsx
index 2e84e99..8c4f86e 100644
--- a/src/examples/BenchmarkRezact/Benchmark.tsx
+++ b/src/examples/BenchmarkRezact/Benchmark.tsx
@@ -41,7 +41,7 @@ export function Page() {
     swapRows = () => {
       if ($data.length > 998) {
         const start = performance.now();
-        $data = swapElements($data.getValue(), 1, 998);
+        $data = swapElements($data.get(), 1, 998);
         $perfTime = performance.now() - start;
       }
     };
diff --git a/src/examples/EscapeHatches/EscapeHatches.tsx b/src/examples/EscapeHatches/EscapeHatches.tsx
new file mode 100644
index 0000000..6bcacaa
--- /dev/null
+++ b/src/examples/EscapeHatches/EscapeHatches.tsx
@@ -0,0 +1,13 @@
+import { MyLayout } from "../Layout/nestedLayout";
+import { TestManual } from "./TestManual";
+
+export function Page() {
+  return (
+    <>
+      <h1>Hello World</h1>
+      <TestManual />
+    </>
+  );
+}
+
+export const Layout = MyLayout;
diff --git a/src/examples/EscapeHatches/index.html b/src/examples/EscapeHatches/index.html
new file mode 100644
index 0000000..cb2b361
--- /dev/null
+++ b/src/examples/EscapeHatches/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Rezact + TS</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./main.ts"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/examples/EscapeHatches/main.ts b/src/examples/EscapeHatches/main.ts
new file mode 100644
index 0000000..5f5272a
--- /dev/null
+++ b/src/examples/EscapeHatches/main.ts
@@ -0,0 +1,5 @@
+import { render } from "rezact";
+import { Page } from "./EscapeHatches";
+
+const app = document.getElementById("app");
+render(app, Page);
diff --git a/src/examples/HelloWorldRezact/HelloWorld.tsx b/src/examples/HelloWorldRezact/HelloWorld.tsx
index f2777bb..b60f268 100644
--- a/src/examples/HelloWorldRezact/HelloWorld.tsx
+++ b/src/examples/HelloWorldRezact/HelloWorld.tsx
@@ -1,4 +1,3 @@
-// import { xCreateElement, xFragment } from "rezact";
 import { MyLayout } from "../Layout/nestedLayout";
 
 export function Page() {
diff --git a/src/examples/Layout/nestedLayout.tsx b/src/examples/Layout/nestedLayout.tsx
index dee75f7..4d364ee 100644
--- a/src/examples/Layout/nestedLayout.tsx
+++ b/src/examples/Layout/nestedLayout.tsx
@@ -68,6 +68,9 @@ export function MyLayout({ Component, pageProps }: any) {
         <li>
           <a href="/simple-string-list">Simple String List</a>
         </li>
+        <li>
+          <a href="/escape-hatches">Escape Hatches</a>
+        </li>
         <li>
           <a href="/asdfasdfasdfasdf">A Route that does not exist</a>
         </li>
diff --git a/src/examples/ListReRender/App.tsx b/src/examples/ListReRender/App.tsx
index 4810516..b7340d0 100644
--- a/src/examples/ListReRender/App.tsx
+++ b/src/examples/ListReRender/App.tsx
@@ -97,7 +97,7 @@ function Viewer({ $item }) {
   return (
     <Display
       $render={$testValue}
-      $value={`${$item.$price.getValue()}, x${$item.$qty.getValue()}`}
+      $value={`${$item.$price.get()}, x${$item.$qty.get()}`}
       $hue={`${$trigger * 360}`}
     />
   );
diff --git a/src/examples/ListReRender/index.html b/src/examples/ListReRender/index.html
new file mode 100644
index 0000000..cb2b361
--- /dev/null
+++ b/src/examples/ListReRender/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Rezact + TS</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./main.ts"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/examples/ListReRender/main.ts b/src/examples/ListReRender/main.ts
new file mode 100644
index 0000000..6656968
--- /dev/null
+++ b/src/examples/ListReRender/main.ts
@@ -0,0 +1,5 @@
+import { render } from "rezact";
+import Page from "./App";
+
+const app = document.getElementById("app");
+render(app, Page);
diff --git a/src/examples/ReactiveComputations/index.html b/src/examples/ReactiveComputations/index.html
new file mode 100644
index 0000000..cb2b361
--- /dev/null
+++ b/src/examples/ReactiveComputations/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Rezact + TS</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./main.ts"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/examples/ReactiveComputations/main.ts b/src/examples/ReactiveComputations/main.ts
new file mode 100644
index 0000000..1c906ab
--- /dev/null
+++ b/src/examples/ReactiveComputations/main.ts
@@ -0,0 +1,5 @@
+import { render } from "rezact";
+import Page from "./ReactiveComp";
+
+const app = document.getElementById("app");
+render(app, Page);
diff --git a/src/examples/SimpleStringListState/index.html b/src/examples/SimpleStringListState/index.html
new file mode 100644
index 0000000..cb2b361
--- /dev/null
+++ b/src/examples/SimpleStringListState/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Rezact + TS</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./main.ts"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/examples/SimpleStringListState/main.ts b/src/examples/SimpleStringListState/main.ts
new file mode 100644
index 0000000..2469f11
--- /dev/null
+++ b/src/examples/SimpleStringListState/main.ts
@@ -0,0 +1,5 @@
+import { render } from "rezact";
+import { Page } from "./SimpleStringList";
+
+const app = document.getElementById("app");
+render(app, Page);
diff --git a/src/examples/Todo/Todo.tsx b/src/examples/Todo/Todo.tsx
index 1cc54f9..eaf9236 100644
--- a/src/examples/Todo/Todo.tsx
+++ b/src/examples/Todo/Todo.tsx
@@ -63,7 +63,7 @@ export function Page() {
 let currentEditingTodo = null;
 function editModeClickListener(ev) {
   if (ev.target === currentEditingTodo.editInput) return;
-  currentEditingTodo.editing.setValue(false);
+  currentEditingTodo.editing.set(false);
   document.removeEventListener("click", editModeClickListener);
 }
 
diff --git a/src/lib/rezact/index.ts b/src/lib/rezact/index.ts
index ff80909..1456f38 100644
--- a/src/lib/rezact/index.ts
+++ b/src/lib/rezact/index.ts
@@ -115,7 +115,7 @@ export function render(root, tagName, attributes: any = {}) {
 }
 export const xFragment = [];
 
-let handleInputValue = null;
+export let handleInputValue = null;
 export function useInputs() {
   if (handleInputValue) return;
   handleInputValue = true;
@@ -154,7 +154,7 @@ export function useInputs() {
       const inpEvType = element.type === "text" || element.type === "number";
       const evType = inpEvType ? "input" : "change";
       element.addEventListener(evType, () => {
-        attributeValue.setValue(getInputVal(element));
+        attributeValue.set(getInputVal(element));
       });
     }
   };
diff --git a/src/lib/rezact/mapState.ts b/src/lib/rezact/mapState.ts
index 4bed02c..d6d5449 100644
--- a/src/lib/rezact/mapState.ts
+++ b/src/lib/rezact/mapState.ts
@@ -1,9 +1,4 @@
-import {
-  BaseState,
-  computeSub,
-  createComputed,
-  overrideCreateComputed,
-} from "./signals";
+import { Signal, computeSub, effect, overrideEffect } from "./signals";
 import {
   addAppendChildHook,
   appendChild,
@@ -16,10 +11,10 @@ import {
   isArray,
 } from ".";
 
-overrideCreateComputed(_createComputed);
+overrideEffect(_effect);
 
-function _createComputed(func: (obj: any) => {}, deps: any[]) {
-  const NewState = deps[0] instanceof MapState ? MapState : BaseState;
+function _effect(func: (obj: any) => {}, deps: any[]) {
+  const NewState = deps[0] instanceof MapSignal ? MapSignal : Signal;
   const newState: any = new NewState(func(deps));
   newState.computed = true;
   const depsLen = deps.length;
@@ -61,7 +56,7 @@ function findNestedStates(obj, results = [], skipKeys = []) {
   return results;
 }
 
-function findNestedMapStates(item) {
+function findNestedMapSignals(item) {
   return findNestedStates(
     item,
     [],
@@ -80,7 +75,7 @@ function findNestedMapStates(item) {
 
 function subscribeToNestedStates(item, mapStateObj) {
   if (!(item.elmRef instanceof HTMLElement)) return;
-  const nestedStates = findNestedMapStates(item.value);
+  const nestedStates = findNestedMapSignals(item.value);
   nestedStates.forEach((state) => {
     item.nestedSubscribed = true;
 
@@ -89,7 +84,7 @@ function subscribeToNestedStates(item, mapStateObj) {
         if (
           mapStateObj.deps &&
           mapStateObj.deps.length > 0 &&
-          mapStateObj.deps[0] instanceof MapState
+          mapStateObj.deps[0] instanceof MapSignal
         ) {
           mapStateObj.deps[0].refresh();
         } else {
@@ -101,7 +96,7 @@ function subscribeToNestedStates(item, mapStateObj) {
   });
 }
 
-export class MapState extends BaseState {
+export class MapSignal extends Signal {
   elmRefCache: any = new Map();
   refreshTimer: any;
   clearCacheTimer: any;
@@ -127,7 +122,7 @@ export class MapState extends BaseState {
       }
       this.deps &&
         this.deps.forEach((dep) => {
-          if (dep instanceof MapState) {
+          if (dep instanceof MapSignal) {
             dep.removeStaleElmRefCacheItems();
           }
         });
@@ -150,13 +145,13 @@ export class MapState extends BaseState {
     const len = this.value.length;
     for (let idx = 0; idx < len; idx++) {
       let item = this.value[idx];
-      if (!item.state) this.value[idx] = item = new BaseState(item);
+      if (!item.state) this.value[idx] = item = new Signal(item);
       item.__private_idx = idx;
       if (func.args[1]) {
         if (!item.idxState) {
-          item.idxState = new BaseState(0);
+          item.idxState = new Signal(0);
         }
-        item.idxState.setValue(idx);
+        item.idxState.set(idx);
       }
 
       let cachedElmRef = this.elmRefCache.get(item);
@@ -199,7 +194,7 @@ export class MapState extends BaseState {
   toJson() {
     return this.value.map((thisVal) => {
       if (thisVal.toJson) return thisVal.toJson();
-      thisVal.getValue ? thisVal.getValue() : thisVal;
+      thisVal.get ? thisVal.get() : thisVal;
     });
   }
 
@@ -377,8 +372,8 @@ childArrayHandler.handler = (parent, child, insertAfter, removeElm) => {
   }
 };
 
-export const createMapped = (func, deps) => {
-  const computed: any = createComputed(func, deps);
+export const mapEffect = (func, deps) => {
+  const computed: any = effect(func, deps);
   computed.deps = deps;
   computed.mapStateObj = false;
   return computed;
diff --git a/src/lib/rezact/signals.ts b/src/lib/rezact/signals.ts
index 12744a7..42efc16 100644
--- a/src/lib/rezact/signals.ts
+++ b/src/lib/rezact/signals.ts
@@ -8,6 +8,7 @@ import {
   createComment,
   createElement,
   createTextNode,
+  handleInputValue,
 } from ".";
 
 let batchSubs = [];
@@ -27,10 +28,10 @@ function runBatch() {
 
     func.stateObj.subs.set(func, true);
     if (func.funcRef) {
-      func.obj.newVal = func.stateObj.getValue();
+      func.obj.newVal = func.stateObj.get();
       func.funcRef(func.obj);
     } else {
-      func(func.stateObj.getValue());
+      func(func.stateObj.get());
     }
     batchSubs.splice(i, 1);
     // console.debug((window as any).totalSubscriberCount++);
@@ -96,7 +97,7 @@ function attachSubs(elm, subFuncs) {
   batchTime = Date.now();
 }
 
-export class BaseState {
+export class Signal {
   state = true;
   value: any = null;
   subs: any = new Map();
@@ -122,13 +123,13 @@ export class BaseState {
     }
   }
 
-  getValue() {
+  get() {
     if (this.value instanceof Text) return this.value.textContent;
     return this.value;
   }
 
-  setValue(newVal: any) {
-    const val = this.getValue();
+  set(newVal: any) {
+    const val = this.get();
 
     if (newVal === val && !isArray(newVal)) return;
 
@@ -175,19 +176,19 @@ export class BaseState {
       return newObj;
     }
 
-    return this.getValue();
+    return this.get();
   }
 }
 
-export const computeSub = (obj) => obj.newState.setValue(obj.func(obj.deps));
+export const computeSub = (obj) => obj.newState.set(obj.func(obj.deps));
 
-export let createComputed = _createComputed;
-export function overrideCreateComputed(func: any) {
-  createComputed = func;
+export let effect = _effect;
+export function overrideEffect(func: any) {
+  effect = func;
 }
 
-function _createComputed(func: (obj: any) => {}, deps: any[]) {
-  const newState: any = new BaseState(func(deps));
+function _effect(func: (obj: any) => {}, deps: any[]) {
+  const newState: any = new Signal(func(deps));
   newState.computed = true;
   const depsLen = deps.length;
   for (let i = 0; i < depsLen; i++) {
@@ -196,20 +197,20 @@ function _createComputed(func: (obj: any) => {}, deps: any[]) {
   return newState;
 }
 
-export function createComputedAttribute(func: (obj: any) => {}, deps: any[]) {
+export function attrEffect(func: (obj: any) => {}, deps: any[]) {
   return { computer: func, deps };
 }
 
 const textTypes = { string: true, number: true };
 function handleStateTypes(parent: any, child: any) {
-  const val = child.getValue();
+  const val = child.get();
   if (textTypes[typeof val]) {
     handleTextNode(parent, child);
   } else if (val instanceof Node && !child.computed) {
     appendChild(parent, val);
   } else if (child.computed) {
     const placeholder = createElement("span");
-    const newState = new BaseState(placeholder);
+    const newState = new Signal(placeholder);
     if (val instanceof Node) newState.value = val;
     child.subscribe((newVal: any) => {
       if (
@@ -217,10 +218,10 @@ function handleStateTypes(parent: any, child: any) {
         newVal === null ||
         newVal === undefined
       )
-        newState.setValue(placeholder);
-      if (newVal instanceof Node) newState.setValue(newVal);
+        newState.set(placeholder);
+      if (newVal instanceof Node) newState.set(newVal);
     });
-    appendChild(parent, newState.getValue());
+    appendChild(parent, newState.get());
   }
 }
 
@@ -235,7 +236,10 @@ addAppendChildHook(childStateHandler);
 const compAttrSub = (o) => o.elm.setAttribute(o.key, o.computer(o.deps));
 const attrSub = (o) => o.elm.setAttribute(o.key, o.newVal);
 const attributeStateHandler = {
-  matches: (_attrs, _key, attrVal) => attrVal.state || attrVal.computer,
+  matches: (_attrs, key, attrVal) =>
+    // skip if useInputs has been called and this is a value or checked attribute
+    !(handleInputValue && (key === "value" || key === "checked")) &&
+    (attrVal.state || attrVal.computer),
   handler: (elm, key, attrVal) => {
     if (attrVal.computer) {
       const { computer, deps } = attrVal;
@@ -248,7 +252,7 @@ const attributeStateHandler = {
         );
       }
     } else {
-      elm.setAttribute(key, attrVal.getValue());
+      elm.setAttribute(key, attrVal.get());
       attrVal.subscribe({ funcRef: attrSub, obj: { elm, key } }, { elm });
     }
   },
@@ -258,7 +262,7 @@ addAttributeHandler(attributeStateHandler);
 
 const textNodeSub = (o) => (o.txtNode.textContent = o.newVal.toString());
 function handleTextNode(parent: any, child: any) {
-  const val = child.getValue();
+  const val = child.get();
   const txtNode = createTextNode(val.toString());
   if (typeof val === "string") child.value = txtNode;
   child.subscribe(
@@ -276,7 +280,7 @@ addAfterRenderHook(() => {
     if (typeof func === "object") {
       func.funcRef(func.obj);
     } else {
-      func(func.stateObj.getValue());
+      func(func.stateObj.get());
     }
   });
   subscFunctionsArr = [];
diff --git a/src/lib/rezact/vite-plugin.ts b/src/lib/rezact/vite-plugin.ts
index 6f8eb11..f79386c 100644
--- a/src/lib/rezact/vite-plugin.ts
+++ b/src/lib/rezact/vite-plugin.ts
@@ -16,11 +16,11 @@ let functionsToRun: any = [];
 let mapDeclarationTracking = {};
 
 function wrapInUseSignal(node) {
-  signalsUsed.BaseState = true;
-  node.wrappedInBaseState = true;
-  magicString.appendLeft(node.start, `new BaseState(`);
+  signalsUsed.Signal = true;
+  node.wrappedInSignal = true;
+  magicString.appendLeft(node.start, `new Signal(`);
   if (node.name && node.name[0] === "$") {
-    magicString.appendRight(node.end, `.getValue())`);
+    magicString.appendRight(node.end, `.get())`);
   } else {
     magicString.appendRight(node.end, `)`);
   }
@@ -38,13 +38,13 @@ function nodeWillWrapInCreateMapped(node) {
   );
 }
 
-function wrapInUseMapState(node, fullNode, objectNode = null) {
+function wrapInUseMapSignal(node, fullNode, objectNode = null) {
   const mapName =
     fullNode.id?.name || objectNode?.id?.name + "." + fullNode.key?.value;
 
   if (mapName) mapDeclarationTracking[mapName] = objectNode || fullNode;
-  mapStateUsed.MapState = true;
-  magicString.appendLeft(node.start, `new MapState(`);
+  mapStateUsed.MapSignal = true;
+  magicString.appendLeft(node.start, `new MapSignal(`);
   magicString.appendRight(node.end, `)`);
 }
 
@@ -74,7 +74,7 @@ function findDependencies(startingNode, excludeDeps = {}) {
       identifiers[node.object.name] = true;
       identifiers[
         node.property.name
-      ] = `${node.object.name}.getValue().${node.property.name}`;
+      ] = `${node.object.name}.get().${node.property.name}`;
     },
 
     Identifier(node: any, _state, ancestors) {
@@ -114,11 +114,8 @@ function wrapInCreateComputed(node, explicitDeps = null, excludeDeps = {}) {
   if (_deps.length === 0) return;
   const deps = _deps.map((dep) => dep.dep || dep);
   const args = _deps.map((dep) => dep.arg || dep);
-  signalsUsed.createComputed = true;
-  magicString.appendLeft(
-    node.start,
-    `createComputed(([${deps.join(",")}]) => `
-  );
+  signalsUsed.effect = true;
+  magicString.appendLeft(node.start, `effect(([${deps.join(",")}]) => `);
   magicString.appendRight(node.end, `, [${args.join(",")}])`);
 }
 
@@ -131,22 +128,19 @@ function wrapInCreateComputedAttribute(
   if (_deps.length === 0) return;
   const deps = _deps.map((dep) => dep.dep || dep);
   const args = _deps.map((dep) => dep.arg || dep);
-  signalsUsed.createComputedAttribute = true;
-  magicString.appendLeft(
-    node.start,
-    `createComputedAttribute(([${deps.join(",")}]) => `
-  );
+  signalsUsed.attrEffect = true;
+  magicString.appendLeft(node.start, `attrEffect(([${deps.join(",")}]) => `);
   magicString.appendRight(node.end, `, [${args.join(",")}])`);
 }
 
 function wrapInCreateMapped(node, explicitDeps = null, excludeDeps = {}) {
-  mapStateUsed.createMapped = true;
+  mapStateUsed.mapEffect = true;
 
   const _deps = explicitDeps || findDependencies(node, excludeDeps);
   if (_deps.length === 0) return;
   const args = _deps.map((dep) => (dep.includes(".") ? "arr" : dep));
   const deps = _deps.map((dep) => dep.arg || dep);
-  signalsUsed.createComputed = true;
+
   let mapStateObj = "";
   if (node.callee.upgradedToRezactMap) {
     const uuid = Math.random().toString(36).slice(-10).replace(".", "x");
@@ -165,7 +159,7 @@ function wrapInCreateMapped(node, explicitDeps = null, excludeDeps = {}) {
       "$m" + uuid
     );
     const declaredMap = mapDeclarationTracking[dependentMap];
-    const decToRun = `let $m${uuid} = createMapped(([${dependentArg}]) => ${dependentMap}.Map((item) => item), [${dependentMap}] );`;
+    const decToRun = `let $m${uuid} = mapEffect(([${dependentArg}]) => ${dependentMap}.Map((item) => item), [${dependentMap}] );`;
     if (declaredMap) {
       magicString.appendLeft(declaredMap.end + 1, decToRun);
     } else {
@@ -177,14 +171,14 @@ function wrapInCreateMapped(node, explicitDeps = null, excludeDeps = {}) {
     // mapStateObj = `, $m${uuid}`;
   }
 
-  magicString.appendLeft(node.start, `createMapped(([${args.join(",")}]) => `);
+  magicString.appendLeft(node.start, `mapEffect(([${args.join(",")}]) => `);
   magicString.appendRight(node.end, `, [${deps.join(",")}] ${mapStateObj})`);
 }
 
 function appendGetValue(node) {
-  if (node.wrappedInBaseState) return;
+  if (node.wrappedInSignal) return;
   node.vTackedOn = true;
-  magicString.appendRight(node.end, `.getValue()`);
+  magicString.appendRight(node.end, `.get()`);
 }
 
 function hasAncestor(ancestors, type) {
@@ -330,8 +324,8 @@ function wrapInSetValue(node, nestedMember = false) {
     compileRezact(leftAst);
     let leftParsed = magicString.toString();
     const leftMinusValue =
-      leftParsed.slice(-11) === ".getValue()"
-        ? leftParsed.slice(0, leftParsed.length - 11)
+      leftParsed.slice(-6) === ".get()"
+        ? leftParsed.slice(0, leftParsed.length - 6)
         : leftParsed;
 
     const rightAst = acorn.parse(right, {
@@ -344,7 +338,7 @@ function wrapInSetValue(node, nestedMember = false) {
     compileRezact(rightAst);
     const rightParsed = magicString.toString();
     // const rightMinusValue =
-    //   rightParsed.slice(-11) === ".getValue()"
+    //   rightParsed.slice(-6) === ".get()"
     //     ? rightParsed.slice(0, rightParsed.length - 11)
     //     : rightParsed;
 
@@ -358,7 +352,7 @@ function wrapInSetValue(node, nestedMember = false) {
       isAssignOperator &&
       node.left.type === "Identifier"
     )
-      leftParsed = `${leftParsed}.getValue()`;
+      leftParsed = `${leftParsed}.get()`;
     if (node.operator === "+=") nestedRightVal = `${leftParsed} + ${right}`;
     if (node.operator === "-=") nestedRightVal = `${leftParsed} - ${right}`;
     if (node.operator === "*=") nestedRightVal = `${leftParsed} * ${right}`;
@@ -368,7 +362,7 @@ function wrapInSetValue(node, nestedMember = false) {
     magicString.overwrite(
       node.start,
       node.end,
-      `${leftMinusValue}.setValue(${nestedRightVal})`
+      `${leftMinusValue}.set(${nestedRightVal})`
     );
     return;
   }
@@ -377,7 +371,7 @@ function wrapInSetValue(node, nestedMember = false) {
   let rightVal =
     node.right?.raw || src.slice(node.right?.start || 0, node.right?.end || 0);
   if (node.right?.value && node.right.value[0] === "$")
-    rightVal = `${node.right.value}.getValue()`;
+    rightVal = `${node.right.value}.get()`;
 
   const operation =
     node.operator === "++" ? "+ 1" : node.operator === "--" ? "- 1" : "";
@@ -387,19 +381,15 @@ function wrapInSetValue(node, nestedMember = false) {
     node.type === "UpdateExpression" &&
     node.argument.name[0] === "$"
   )
-    rightVal = `${node.argument.name}.getValue() ${operation}`;
+    rightVal = `${node.argument.name}.get() ${operation}`;
 
   if (
     node.right?.type === "UnaryExpression" &&
     node.right.argument.name[0] === "$"
   )
-    rightVal = `${node.right.operator}${node.right.argument.name}.getValue()`;
+    rightVal = `${node.right.operator}${node.right.argument.name}.get()`;
 
-  magicString.overwrite(
-    node.start,
-    node.end,
-    `${leftVal}.setValue(${rightVal})`
-  );
+  magicString.overwrite(node.start, node.end, `${leftVal}.set(${rightVal})`);
 }
 
 function isReactiveAttribute(node) {
@@ -423,7 +413,7 @@ function compileRezact(ast) {
         if (node.init.type === "BinaryExpression")
           wrapInCreateComputed(node.init);
         if (node.init.type === "ArrayExpression")
-          wrapInUseMapState(node.init, node);
+          wrapInUseMapSignal(node.init, node);
         if (
           node.init.type === "CallExpression" &&
           !nodeWillWrapInCreateMapped(node.init)
@@ -680,7 +670,7 @@ function compileRezact(ast) {
         node.key.name[0] === "$" &&
         node.value.type === "ArrayExpression"
       ) {
-        return wrapInUseMapState(node.value, node, ancestors.at(-3));
+        return wrapInUseMapSignal(node.value, node, ancestors.at(-3));
       }
 
       if (
@@ -688,7 +678,7 @@ function compileRezact(ast) {
         node.key.value[0] === "$" &&
         node.value.type === "ArrayExpression"
       ) {
-        return wrapInUseMapState(node.value, node, ancestors.at(-3));
+        return wrapInUseMapSignal(node.value, node, ancestors.at(-3));
       }
 
       if (
@@ -808,6 +798,8 @@ function rezact(): PluginOption {
       magicString = null;
       lastImport = { end: 0 };
       importsUsed = {};
+      signalsUsed = {};
+      mapStateUsed = {};
       functionsToRun = [];
       mapDeclarationTracking = {};
       // console.log(id);


Test patch:
diff --git a/src/examples/CounterRezact/counter.test.tsx b/src/examples/CounterRezact/counter.test.tsx
index 2d26eff..a046098 100644
--- a/src/examples/CounterRezact/counter.test.tsx
+++ b/src/examples/CounterRezact/counter.test.tsx
@@ -40,21 +40,21 @@ describe("counter suite name", () => {
       </h1>
     );
 
-    expect(mountCalledStateRef.getValue()).toBe(null);
-    expect(unmountCalledStateRef.getValue()).toBe(null);
+    expect(mountCalledStateRef.get()).toBe(null);
+    expect(unmountCalledStateRef.get()).toBe(null);
 
     render(document.body, () => testElm);
 
     const elmOnScreen = screen.getByText(/Mount Unmount Test/i);
 
-    await waitFor(() => expect(mountCalledStateRef.getValue()).toBe(true));
-    await waitFor(() => expect(unmountCalledStateRef.getValue()).toBe(null));
+    await waitFor(() => expect(mountCalledStateRef.get()).toBe(true));
+    await waitFor(() => expect(unmountCalledStateRef.get()).toBe(null));
 
     setTimeout(() => testElm.remove(), 20);
 
     await waitForElementToBeRemoved(elmOnScreen);
 
-    await waitFor(() => expect(mountCalledStateRef.getValue()).toBe(true));
-    await waitFor(() => expect(unmountCalledStateRef.getValue()).toBe(true));
+    await waitFor(() => expect(mountCalledStateRef.get()).toBe(true));
+    await waitFor(() => expect(unmountCalledStateRef.get()).toBe(true));
   });
 });
diff --git a/src/examples/EscapeHatches/TestManual.tsx b/src/examples/EscapeHatches/TestManual.tsx
new file mode 100644
index 0000000..711b7e8
--- /dev/null
+++ b/src/examples/EscapeHatches/TestManual.tsx
@@ -0,0 +1,43 @@
+import { Signal, effect } from "rezact/signals";
+import { MapSignal, mapEffect } from "rezact/mapState";
+
+export function TestManual() {
+  let name = new Signal("jesen");
+  let width = new Signal(10);
+  let height = new Signal(20);
+
+  let area = effect(
+    ([width, height]) => width.get() * height.get(),
+    [width, height]
+  );
+
+  let test = new MapSignal(["Jack", "Jill", "John", "Jane"]);
+  let test1 = mapEffect(([test]) => test.Map((item) => item), [test]);
+  let test2 = mapEffect(([test]) => test.Map((item) => item), [test]);
+
+  return (
+    <div>
+      <div id="iss14-1">Hallo, {name}</div>
+      <div id="iss14-2">width: {width}</div>
+      <div id="iss14-3">height: {height}</div>
+      <div id="iss14-4">area: {area}</div>
+      <button onClick={() => width.set(width.get() + 30)}>Update Width</button>
+
+      <h1>Simple String List</h1>
+
+      {mapEffect(
+        ([test1Arr]) =>
+          test1Arr.Map((name) => (
+            <div dataVal={name}>
+              <input value={name} />
+            </div>
+          )),
+        [test1]
+      )}
+      {mapEffect(
+        ([test2Arr]) => test2Arr.Map((name) => <div>{name}</div>),
+        [test2]
+      )}
+    </div>
+  );
+}
diff --git a/src/examples/EscapeHatches/escapehatches.test.ts b/src/examples/EscapeHatches/escapehatches.test.ts
new file mode 100644
index 0000000..ab7cee1
--- /dev/null
+++ b/src/examples/EscapeHatches/escapehatches.test.ts
@@ -0,0 +1,12 @@
+import { describe, expect, it } from "vitest";
+import { screen } from "@testing-library/dom";
+import { render } from "rezact";
+import { Page } from "./EscapeHatches";
+
+describe("Escape Hatch suite", () => {
+  it("Renders Escape Hatch Demo", () => {
+    render(document.body, Page);
+    const allHeaders = screen.getAllByText(/hello world/i);
+    expect(allHeaders).toHaveLength(1);
+  });
+});
diff --git a/src/examples/StoreTest/index.html b/src/examples/StoreTest/index.html
new file mode 100644
index 0000000..cb2b361
--- /dev/null
+++ b/src/examples/StoreTest/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Rezact + TS</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="./main.ts"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/examples/StoreTest/main.ts b/src/examples/StoreTest/main.ts
new file mode 100644
index 0000000..6656968
--- /dev/null
+++ b/src/examples/StoreTest/main.ts
@@ -0,0 +1,5 @@
+import { render } from "rezact";
+import Page from "./App";
+
+const app = document.getElementById("app");
+render(app, Page);