rask-ui 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -38
- package/dist/component.d.ts +12 -4
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +22 -5
- package/dist/createComputed.d.ts.map +1 -1
- package/dist/createComputed.js +36 -0
- package/dist/createState.js +31 -13
- package/dist/createView.d.ts.map +1 -1
- package/dist/createView.js +28 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inspect.d.ts +12 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +9 -2
- package/package.json +1 -1
- package/swc-plugin/src/lib.rs +177 -48
- package/swc-plugin/target/wasm32-wasip1/release/deps/libswc_plugin_rask_component.rlib +0 -0
- package/swc-plugin/target/wasm32-wasip1/release/deps/swc_plugin_rask_component.wasm +0 -0
- package/swc-plugin/target/wasm32-wasip1/release/libswc_plugin_rask_component.rlib +0 -0
- package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
package/README.md
CHANGED
|
@@ -684,12 +684,12 @@ function Child() {
|
|
|
684
684
|
|
|
685
685
|
#### `createTask<T, P>(task)`
|
|
686
686
|
|
|
687
|
-
|
|
687
|
+
A low-level reactive primitive for managing any async operation. `createTask` provides the foundation for building data fetching, mutations, polling, debouncing, and any other async pattern you need. It gives you full control without prescribing specific patterns.
|
|
688
688
|
|
|
689
689
|
```tsx
|
|
690
|
-
import { createTask } from "rask-ui";
|
|
690
|
+
import { createTask, createState } from "rask-ui";
|
|
691
691
|
|
|
692
|
-
//
|
|
692
|
+
// Fetching data - auto-runs on creation
|
|
693
693
|
function UserProfile() {
|
|
694
694
|
const user = createTask(() => fetch("/api/user").then((r) => r.json()));
|
|
695
695
|
|
|
@@ -706,71 +706,116 @@ function UserProfile() {
|
|
|
706
706
|
};
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
-
//
|
|
709
|
+
// Fetching with parameters
|
|
710
710
|
function Posts() {
|
|
711
|
+
const state = createState({ page: 1 });
|
|
712
|
+
|
|
711
713
|
const posts = createTask((page: number) =>
|
|
712
|
-
fetch(`/api/posts?page=${page}`).then((r) => r.json())
|
|
714
|
+
fetch(`/api/posts?page=${page}&limit=10`).then((r) => r.json())
|
|
713
715
|
);
|
|
714
716
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (posts.error) {
|
|
721
|
-
return <p>Error: {posts.error}</p>;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
if (!posts.result) {
|
|
725
|
-
return <p>No posts loaded</p>;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
return posts.result.map((post) => (
|
|
729
|
-
<article key={post.id}>{post.title}</article>
|
|
730
|
-
));
|
|
731
|
-
};
|
|
717
|
+
// Fetch when page changes
|
|
718
|
+
createEffect(() => {
|
|
719
|
+
posts.run(state.page);
|
|
720
|
+
});
|
|
732
721
|
|
|
733
722
|
return () => (
|
|
734
723
|
<div>
|
|
735
|
-
<
|
|
736
|
-
|
|
737
|
-
{
|
|
724
|
+
<h1>Posts - Page {state.page}</h1>
|
|
725
|
+
{posts.isRunning && <p>Loading...</p>}
|
|
726
|
+
{posts.result?.map((post) => (
|
|
727
|
+
<article key={post.id}>{post.title}</article>
|
|
728
|
+
))}
|
|
729
|
+
<button onClick={() => state.page--}>Previous</button>
|
|
730
|
+
<button onClick={() => state.page++}>Next</button>
|
|
738
731
|
</div>
|
|
739
732
|
);
|
|
740
733
|
}
|
|
741
734
|
|
|
742
|
-
// Mutation-
|
|
735
|
+
// Mutation - creating data on server
|
|
743
736
|
function CreatePost() {
|
|
744
737
|
const state = createState({ title: "", body: "" });
|
|
745
738
|
|
|
746
|
-
const
|
|
739
|
+
const createPost = createTask((data: { title: string; body: string }) =>
|
|
747
740
|
fetch("/api/posts", {
|
|
748
741
|
method: "POST",
|
|
742
|
+
headers: { "Content-Type": "application/json" },
|
|
749
743
|
body: JSON.stringify(data),
|
|
750
744
|
}).then((r) => r.json())
|
|
751
745
|
);
|
|
752
746
|
|
|
753
|
-
const handleSubmit = () => {
|
|
754
|
-
|
|
747
|
+
const handleSubmit = async () => {
|
|
748
|
+
await createPost.run({ title: state.title, body: state.body });
|
|
749
|
+
// Clear form on success
|
|
750
|
+
state.title = "";
|
|
751
|
+
state.body = "";
|
|
755
752
|
};
|
|
756
753
|
|
|
757
754
|
return () => (
|
|
758
755
|
<form onSubmit={handleSubmit}>
|
|
759
756
|
<input
|
|
757
|
+
placeholder="Title"
|
|
760
758
|
value={state.title}
|
|
761
759
|
onInput={(e) => (state.title = e.target.value)}
|
|
762
760
|
/>
|
|
763
761
|
<textarea
|
|
762
|
+
placeholder="Body"
|
|
764
763
|
value={state.body}
|
|
765
764
|
onInput={(e) => (state.body = e.target.value)}
|
|
766
765
|
/>
|
|
767
|
-
<button disabled={
|
|
768
|
-
{
|
|
766
|
+
<button disabled={createPost.isRunning}>
|
|
767
|
+
{createPost.isRunning ? "Creating..." : "Create Post"}
|
|
769
768
|
</button>
|
|
770
|
-
{
|
|
769
|
+
{createPost.error && <p>Error: {createPost.error}</p>}
|
|
770
|
+
{createPost.result && <p>Post created! ID: {createPost.result.id}</p>}
|
|
771
771
|
</form>
|
|
772
772
|
);
|
|
773
773
|
}
|
|
774
|
+
|
|
775
|
+
// Optimistic updates - instant UI updates with rollback on error
|
|
776
|
+
function TodoList() {
|
|
777
|
+
const state = createState({
|
|
778
|
+
todos: [],
|
|
779
|
+
optimisticTodo: null,
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const createTodo = createTask((text: string) =>
|
|
783
|
+
fetch("/api/todos", {
|
|
784
|
+
method: "POST",
|
|
785
|
+
headers: { "Content-Type": "application/json" },
|
|
786
|
+
body: JSON.stringify({ text, done: false }),
|
|
787
|
+
}).then((r) => r.json())
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
const addTodo = async (text: string) => {
|
|
791
|
+
// Show optimistically
|
|
792
|
+
state.optimisticTodo = { id: Date.now(), text, done: false };
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
const savedTodo = await createTodo.run(text);
|
|
796
|
+
state.todos.push(savedTodo);
|
|
797
|
+
state.optimisticTodo = null;
|
|
798
|
+
} catch {
|
|
799
|
+
// Rollback on error
|
|
800
|
+
state.optimisticTodo = null;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
return () => (
|
|
805
|
+
<div>
|
|
806
|
+
<ul>
|
|
807
|
+
{[...state.todos, state.optimisticTodo]
|
|
808
|
+
.filter(Boolean)
|
|
809
|
+
.map((todo) => (
|
|
810
|
+
<li key={todo.id} style={{ opacity: todo === state.optimisticTodo ? 0.5 : 1 }}>
|
|
811
|
+
{todo.text}
|
|
812
|
+
</li>
|
|
813
|
+
))}
|
|
814
|
+
</ul>
|
|
815
|
+
<button onClick={() => addTodo("New todo")}>Add Todo</button>
|
|
816
|
+
</div>
|
|
817
|
+
);
|
|
818
|
+
}
|
|
774
819
|
```
|
|
775
820
|
|
|
776
821
|
**Type Signatures:**
|
|
@@ -833,12 +878,208 @@ Error:
|
|
|
833
878
|
|
|
834
879
|
**Usage Patterns:**
|
|
835
880
|
|
|
836
|
-
|
|
837
|
-
- **
|
|
838
|
-
- **Mutations**:
|
|
839
|
-
- **
|
|
840
|
-
- **
|
|
841
|
-
- **
|
|
881
|
+
`createTask` is a low-level primitive. Use it as a building block for any async pattern:
|
|
882
|
+
- **Data fetching**: Fetch data on mount or based on dependencies
|
|
883
|
+
- **Mutations**: Create, update, or delete data on the server
|
|
884
|
+
- **Optimistic updates**: Update UI instantly with rollback on error
|
|
885
|
+
- **Polling**: Periodically refetch data
|
|
886
|
+
- **Debounced searches**: Wait for user input to settle
|
|
887
|
+
- **Dependent queries**: Chain requests that depend on each other
|
|
888
|
+
- **Parallel requests**: Run multiple requests simultaneously
|
|
889
|
+
- **Custom patterns**: Build your own abstractions (queries, mutations, etc.)
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
### Developer Tools
|
|
894
|
+
|
|
895
|
+
#### `inspect(root, callback)`
|
|
896
|
+
|
|
897
|
+
Enables inspection of reactive state, computed values, and actions for building devtools. This API is **development-only** and has zero overhead in production builds.
|
|
898
|
+
|
|
899
|
+
```tsx
|
|
900
|
+
import { inspect } from "rask-ui";
|
|
901
|
+
|
|
902
|
+
function DevToolsIntegration() {
|
|
903
|
+
const state = createState({ count: 0, name: "Example" });
|
|
904
|
+
const computed = createComputed({
|
|
905
|
+
double: () => state.count * 2,
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const actions = {
|
|
909
|
+
increment: () => state.count++,
|
|
910
|
+
reset: () => state.count = 0,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
const view = createView(state, computed, actions);
|
|
914
|
+
|
|
915
|
+
// Inspect all reactive events
|
|
916
|
+
inspect(view, (event) => {
|
|
917
|
+
console.log(event);
|
|
918
|
+
// Send to devtools panel, logging service, etc.
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
return () => (
|
|
922
|
+
<div>
|
|
923
|
+
<h1>{view.name}: {view.count}</h1>
|
|
924
|
+
<p>Double: {view.double}</p>
|
|
925
|
+
<button onClick={view.increment}>+</button>
|
|
926
|
+
<button onClick={view.reset}>Reset</button>
|
|
927
|
+
</div>
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
**Parameters:**
|
|
933
|
+
|
|
934
|
+
- `root: any` - The reactive object to inspect (state, view, computed, etc.)
|
|
935
|
+
- `callback: (event: InspectEvent) => void` - Callback receiving inspection events
|
|
936
|
+
|
|
937
|
+
**Event Types:**
|
|
938
|
+
|
|
939
|
+
```tsx
|
|
940
|
+
type InspectEvent =
|
|
941
|
+
| {
|
|
942
|
+
type: "mutation";
|
|
943
|
+
path: string[]; // Property path, e.g., ["user", "name"]
|
|
944
|
+
value: any; // New value
|
|
945
|
+
}
|
|
946
|
+
| {
|
|
947
|
+
type: "action";
|
|
948
|
+
path: string[]; // Function name path, e.g., ["increment"]
|
|
949
|
+
params: any[]; // Function parameters
|
|
950
|
+
}
|
|
951
|
+
| {
|
|
952
|
+
type: "computed";
|
|
953
|
+
path: string[]; // Computed property path
|
|
954
|
+
isDirty: boolean; // true when invalidated, false when recomputed
|
|
955
|
+
value: any; // Current or recomputed value
|
|
956
|
+
};
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
**Features:**
|
|
960
|
+
|
|
961
|
+
- **Zero production overhead** - Completely eliminated in production builds via tree-shaking
|
|
962
|
+
- **Deep tracking** - Tracks nested state mutations with full property paths
|
|
963
|
+
- **Action tracking** - Captures function calls with parameters
|
|
964
|
+
- **Computed lifecycle** - Observes when computed values become dirty and when they recompute
|
|
965
|
+
- **Nested view support** - Compose deeply nested state trees and track full paths
|
|
966
|
+
- **JSON serialization** - Use `JSON.stringify()` to extract state snapshots
|
|
967
|
+
- **Flexible integration** - Build custom devtools, time-travel debugging, or logging systems
|
|
968
|
+
|
|
969
|
+
**Use Cases:**
|
|
970
|
+
|
|
971
|
+
- Building browser devtools extensions
|
|
972
|
+
- Creating debugging panels for development
|
|
973
|
+
- Implementing time-travel debugging
|
|
974
|
+
- Logging state changes for debugging
|
|
975
|
+
- Integrating with external monitoring tools
|
|
976
|
+
- Building replay systems for bug reports
|
|
977
|
+
|
|
978
|
+
**Production Builds:**
|
|
979
|
+
|
|
980
|
+
The inspector is automatically stripped from production builds using Vite's `import.meta.env.DEV` constant. This means:
|
|
981
|
+
|
|
982
|
+
- No runtime checks in production code
|
|
983
|
+
- No function wrapping overhead
|
|
984
|
+
- No symbol property lookups
|
|
985
|
+
- Smaller bundle size
|
|
986
|
+
- Zero performance impact
|
|
987
|
+
|
|
988
|
+
```tsx
|
|
989
|
+
// In development
|
|
990
|
+
inspect(view, console.log); // ✅ Works, logs all events
|
|
991
|
+
|
|
992
|
+
// In production build
|
|
993
|
+
inspect(view, console.log); // No-op, zero overhead, removed by tree-shaking
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
**Nested State Trees:**
|
|
997
|
+
|
|
998
|
+
The inspector automatically tracks nested property paths. Compose deeply nested views to create organized state trees:
|
|
999
|
+
|
|
1000
|
+
```tsx
|
|
1001
|
+
function App() {
|
|
1002
|
+
// Create nested state structure
|
|
1003
|
+
const userState = createState({
|
|
1004
|
+
profile: { name: "Alice", email: "alice@example.com" },
|
|
1005
|
+
preferences: { theme: "dark", notifications: true },
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
const cartState = createState({
|
|
1009
|
+
items: [],
|
|
1010
|
+
total: 0,
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Compose into a state tree
|
|
1014
|
+
const appState = createView({
|
|
1015
|
+
user: createView(userState),
|
|
1016
|
+
cart: createView(cartState),
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
inspect(appState, (event) => {
|
|
1020
|
+
console.log(event);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// When you do: appState.user.profile.name = "Bob"
|
|
1024
|
+
// You receive: { type: "mutation", path: ["user", "profile", "name"], value: "Bob" }
|
|
1025
|
+
|
|
1026
|
+
return () => (
|
|
1027
|
+
<div>
|
|
1028
|
+
<h1>Welcome, {appState.user.profile.name}!</h1>
|
|
1029
|
+
<p>Theme: {appState.user.preferences.theme}</p>
|
|
1030
|
+
<p>Cart items: {appState.cart.items.length}</p>
|
|
1031
|
+
</div>
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
**JSON Serialization:**
|
|
1037
|
+
|
|
1038
|
+
Use `JSON.stringify()` to extract state snapshots for devtools, persistence, or debugging:
|
|
1039
|
+
|
|
1040
|
+
```tsx
|
|
1041
|
+
function App() {
|
|
1042
|
+
const state = createState({
|
|
1043
|
+
user: { id: 1, name: "Alice" },
|
|
1044
|
+
todos: [
|
|
1045
|
+
{ id: 1, text: "Learn RASK", done: false },
|
|
1046
|
+
{ id: 2, text: "Build app", done: true },
|
|
1047
|
+
],
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const computed = createComputed({
|
|
1051
|
+
completedCount: () => state.todos.filter((t) => t.done).length,
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
const view = createView(state, computed);
|
|
1055
|
+
|
|
1056
|
+
// Serialize to JSON - includes computed values
|
|
1057
|
+
const snapshot = JSON.stringify(view);
|
|
1058
|
+
// {
|
|
1059
|
+
// "user": { "id": 1, "name": "Alice" },
|
|
1060
|
+
// "todos": [...],
|
|
1061
|
+
// "completedCount": 1
|
|
1062
|
+
// }
|
|
1063
|
+
|
|
1064
|
+
// Send initial state and updates to devtools
|
|
1065
|
+
if (import.meta.env.DEV) {
|
|
1066
|
+
window.postMessage({
|
|
1067
|
+
type: "RASK_DEVTOOLS_INIT",
|
|
1068
|
+
initialState: JSON.parse(snapshot),
|
|
1069
|
+
}, "*");
|
|
1070
|
+
|
|
1071
|
+
inspect(view, (event) => {
|
|
1072
|
+
window.postMessage({
|
|
1073
|
+
type: "RASK_DEVTOOLS_EVENT",
|
|
1074
|
+
event,
|
|
1075
|
+
snapshot: JSON.parse(JSON.stringify(view)),
|
|
1076
|
+
}, "*");
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return () => <div>{/* Your app */}</div>;
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
842
1083
|
|
|
843
1084
|
---
|
|
844
1085
|
|
package/dist/component.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { VNode, Component, Props } from "inferno";
|
|
2
|
-
|
|
2
|
+
import { Observer } from "./observation";
|
|
3
|
+
export type RaskStatelessFunctionComponent<P extends Props<any>> = (() => VNode) | ((props: P) => VNode);
|
|
4
|
+
export declare class RaskStatelessComponent extends Component {
|
|
5
|
+
renderFn: RaskStatelessFunctionComponent<any>;
|
|
6
|
+
observer: Observer;
|
|
7
|
+
shouldComponentUpdate(nextProps: Props<any>): boolean;
|
|
8
|
+
render(): VNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function getCurrentComponent(): RaskStatefulComponent<any>;
|
|
3
11
|
export declare function createMountEffect(cb: () => void): void;
|
|
4
12
|
export declare function createCleanup(cb: () => void): void;
|
|
5
|
-
export type
|
|
6
|
-
export declare class
|
|
7
|
-
setup:
|
|
13
|
+
export type RaskStatefulFunctionComponent<P extends Props<any>> = (() => () => VNode) | ((props: P) => () => VNode);
|
|
14
|
+
export declare class RaskStatefulComponent<P extends Props<any>> extends Component<P> {
|
|
15
|
+
setup: RaskStatefulFunctionComponent<P>;
|
|
8
16
|
private renderFn?;
|
|
9
17
|
private reactiveProps?;
|
|
10
18
|
private observer;
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EAEN,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EAEN,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAsB,QAAQ,EAAU,MAAM,eAAe,CAAC;AAGrE,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC3D,CAAC,MAAM,KAAK,CAAC,GACb,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC;AAE1B,qBAAa,sBAAuB,SAAQ,SAAS;IAC3C,QAAQ,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACtD,QAAQ,WAEL;IACH,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO;IAUrD,MAAM;CAMP;AAID,wBAAgB,mBAAmB,+BAMlC;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,IAAI,QAM/C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI,QAM3C;AAED,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC1D,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,qBAAa,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IACnE,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,QAAQ,CAEb;IACH,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACnC,OAAO,CAAC,mBAAmB;IA2D3B,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAG5B;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,GAAG;IAYlC,yBAAyB,IAAI,IAAI;IACjC,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO;IAWrD,MAAM;CAyCP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
|
package/dist/component.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { createComponentVNode, Component, } from "inferno";
|
|
2
2
|
import { getCurrentObserver, Observer, Signal } from "./observation";
|
|
3
3
|
import { syncBatch } from "./batch";
|
|
4
|
+
export class RaskStatelessComponent extends Component {
|
|
5
|
+
observer = new Observer(() => {
|
|
6
|
+
this.forceUpdate();
|
|
7
|
+
});
|
|
8
|
+
shouldComponentUpdate(nextProps) {
|
|
9
|
+
for (const prop in nextProps) {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
if (this.props[prop] !== nextProps[prop]) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
const stopObserving = this.observer.observe();
|
|
19
|
+
const result = this.renderFn(this.props);
|
|
20
|
+
stopObserving();
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
4
24
|
let currentComponent;
|
|
5
25
|
export function getCurrentComponent() {
|
|
6
26
|
if (!currentComponent) {
|
|
@@ -20,7 +40,7 @@ export function createCleanup(cb) {
|
|
|
20
40
|
}
|
|
21
41
|
currentComponent.onCleanups.push(cb);
|
|
22
42
|
}
|
|
23
|
-
export class
|
|
43
|
+
export class RaskStatefulComponent extends Component {
|
|
24
44
|
renderFn;
|
|
25
45
|
reactiveProps;
|
|
26
46
|
observer = new Observer(() => {
|
|
@@ -113,9 +133,6 @@ export class RaskComponent extends Component {
|
|
|
113
133
|
shouldComponentUpdate(nextProps) {
|
|
114
134
|
// Shallow comparison of props, excluding internal props
|
|
115
135
|
for (const prop in nextProps) {
|
|
116
|
-
if (prop === "__component") {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
136
|
// @ts-ignore
|
|
120
137
|
if (this.props[prop] !== nextProps[prop]) {
|
|
121
138
|
return true;
|
|
@@ -162,5 +179,5 @@ export class RaskComponent extends Component {
|
|
|
162
179
|
}
|
|
163
180
|
}
|
|
164
181
|
export function createComponent(props, key) {
|
|
165
|
-
return createComponentVNode(4 /* VNodeFlags.ComponentClass */,
|
|
182
|
+
return createComponentVNode(4 /* VNodeFlags.ComponentClass */, RaskStatefulComponent, props, key);
|
|
166
183
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createComputed.d.ts","sourceRoot":"","sources":["../src/createComputed.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createComputed.d.ts","sourceRoot":"","sources":["../src/createComputed.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,EAChE,QAAQ,EAAE,CAAC,GACV;KACA,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACjC,CAiFA"}
|
package/dist/createComputed.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCurrentComponent, createCleanup } from "./component";
|
|
2
|
+
import { INSPECT_MARKER, INSPECTOR_ENABLED } from "./inspect";
|
|
2
3
|
import { getCurrentObserver, Observer, Signal } from "./observation";
|
|
3
4
|
export function createComputed(computed) {
|
|
4
5
|
let currentComponent;
|
|
@@ -9,6 +10,7 @@ export function createComputed(computed) {
|
|
|
9
10
|
currentComponent = undefined;
|
|
10
11
|
}
|
|
11
12
|
const proxy = {};
|
|
13
|
+
let notifyInspectorRef = {};
|
|
12
14
|
for (const prop in computed) {
|
|
13
15
|
let isDirty = true;
|
|
14
16
|
let value;
|
|
@@ -16,11 +18,21 @@ export function createComputed(computed) {
|
|
|
16
18
|
const computedObserver = new Observer(() => {
|
|
17
19
|
isDirty = true;
|
|
18
20
|
signal.notify();
|
|
21
|
+
if (INSPECTOR_ENABLED) {
|
|
22
|
+
notifyInspectorRef.current?.notify({
|
|
23
|
+
type: "computed",
|
|
24
|
+
path: notifyInspectorRef.current.path.concat(prop),
|
|
25
|
+
isDirty: true,
|
|
26
|
+
value,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
19
29
|
});
|
|
20
30
|
if (currentComponent) {
|
|
21
31
|
createCleanup(() => computedObserver.dispose());
|
|
22
32
|
}
|
|
23
33
|
Object.defineProperty(proxy, prop, {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
24
36
|
get() {
|
|
25
37
|
const currentObserver = getCurrentObserver();
|
|
26
38
|
if (currentObserver) {
|
|
@@ -31,11 +43,35 @@ export function createComputed(computed) {
|
|
|
31
43
|
value = computed[prop]();
|
|
32
44
|
stopObserving();
|
|
33
45
|
isDirty = false;
|
|
46
|
+
if (INSPECTOR_ENABLED) {
|
|
47
|
+
notifyInspectorRef.current?.notify({
|
|
48
|
+
type: "computed",
|
|
49
|
+
path: notifyInspectorRef.current.path.concat(prop),
|
|
50
|
+
isDirty: false,
|
|
51
|
+
value,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
34
54
|
return value;
|
|
35
55
|
}
|
|
36
56
|
return value;
|
|
37
57
|
},
|
|
38
58
|
});
|
|
39
59
|
}
|
|
60
|
+
if (INSPECTOR_ENABLED) {
|
|
61
|
+
Object.defineProperty(proxy, INSPECT_MARKER, {
|
|
62
|
+
enumerable: false,
|
|
63
|
+
configurable: false,
|
|
64
|
+
get() {
|
|
65
|
+
return !notifyInspectorRef.current;
|
|
66
|
+
},
|
|
67
|
+
set: (value) => {
|
|
68
|
+
Object.defineProperty(notifyInspectorRef, "current", {
|
|
69
|
+
get() {
|
|
70
|
+
return value.current;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
40
76
|
return proxy;
|
|
41
77
|
}
|
package/dist/createState.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INSPECT_MARKER } from "./inspect";
|
|
1
|
+
import { INSPECT_MARKER, INSPECTOR_ENABLED } from "./inspect";
|
|
2
2
|
import { getCurrentObserver, Signal } from "./observation";
|
|
3
3
|
/**
|
|
4
4
|
* Creates a reactive state object that tracks property access and notifies observers on changes.
|
|
@@ -25,11 +25,11 @@ import { getCurrentObserver, Signal } from "./observation";
|
|
|
25
25
|
* @returns A reactive proxy of the state object
|
|
26
26
|
*/
|
|
27
27
|
export function createState(state) {
|
|
28
|
-
return getProxy(state);
|
|
28
|
+
return getProxy(state, {});
|
|
29
29
|
}
|
|
30
30
|
const proxyCache = new WeakMap();
|
|
31
31
|
export const PROXY_MARKER = Symbol("isProxy");
|
|
32
|
-
function getProxy(value,
|
|
32
|
+
function getProxy(value, notifyInspectorRef) {
|
|
33
33
|
// Check if already a proxy to avoid double-wrapping
|
|
34
34
|
if (PROXY_MARKER in value) {
|
|
35
35
|
return value;
|
|
@@ -44,13 +44,19 @@ function getProxy(value, notifyInspector, path) {
|
|
|
44
44
|
if (key === PROXY_MARKER) {
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
|
+
if (INSPECTOR_ENABLED && key === INSPECT_MARKER) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
47
50
|
return Reflect.has(target, key);
|
|
48
51
|
},
|
|
49
52
|
get(target, key) {
|
|
50
53
|
// Mark this as a proxy to prevent double-wrapping
|
|
51
|
-
if (key === PROXY_MARKER
|
|
54
|
+
if (key === PROXY_MARKER) {
|
|
52
55
|
return true;
|
|
53
56
|
}
|
|
57
|
+
if (INSPECTOR_ENABLED && key === INSPECT_MARKER) {
|
|
58
|
+
return !notifyInspectorRef.current;
|
|
59
|
+
}
|
|
54
60
|
const value = Reflect.get(target, key);
|
|
55
61
|
if (typeof key === "symbol" || typeof value === "function") {
|
|
56
62
|
return value;
|
|
@@ -62,14 +68,24 @@ function getProxy(value, notifyInspector, path) {
|
|
|
62
68
|
}
|
|
63
69
|
if (Array.isArray(value) ||
|
|
64
70
|
(typeof value === "object" && value !== null)) {
|
|
65
|
-
return getProxy(value,
|
|
71
|
+
return getProxy(value, INSPECTOR_ENABLED && notifyInspectorRef.current
|
|
72
|
+
? {
|
|
73
|
+
current: {
|
|
74
|
+
notify: notifyInspectorRef.current.notify,
|
|
75
|
+
path: notifyInspectorRef.current.path.concat(key),
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: notifyInspectorRef);
|
|
66
79
|
}
|
|
67
80
|
return value;
|
|
68
81
|
},
|
|
69
82
|
set(target, key, newValue) {
|
|
70
|
-
if (key === INSPECT_MARKER) {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
if (INSPECTOR_ENABLED && key === INSPECT_MARKER) {
|
|
84
|
+
Object.defineProperty(notifyInspectorRef, "current", {
|
|
85
|
+
get() {
|
|
86
|
+
return newValue.current;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
73
89
|
return Reflect.set(target, key, newValue);
|
|
74
90
|
}
|
|
75
91
|
if (typeof key === "symbol") {
|
|
@@ -82,11 +98,13 @@ function getProxy(value, notifyInspector, path) {
|
|
|
82
98
|
const signal = signals[key];
|
|
83
99
|
signal?.notify();
|
|
84
100
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
if (INSPECTOR_ENABLED) {
|
|
102
|
+
notifyInspectorRef.current?.notify({
|
|
103
|
+
type: "mutation",
|
|
104
|
+
path: notifyInspectorRef.current.path,
|
|
105
|
+
value: newValue,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
90
108
|
return setResult;
|
|
91
109
|
},
|
|
92
110
|
deleteProperty(target, key) {
|
package/dist/createView.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createView.d.ts","sourceRoot":"","sources":["../src/createView.ts"],"names":[],"mappings":"AAGA,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AAEjD,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,IAAI,QAAQ,CAC1D,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CACrB,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,IAAI,CAAC,SAAS;IACtD,MAAM,CAAC,SAAS,MAAM;IACtB,GAAG,MAAM,CAAC,SAAS,MAAM,EAAE;CAC5B,GACG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GACzB,EAAE,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACpD,GAAG,IAAI,EAAE,CAAC,GACT,SAAS,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"createView.d.ts","sourceRoot":"","sources":["../src/createView.ts"],"names":[],"mappings":"AAGA,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AAEjD,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,IAAI,QAAQ,CAC1D,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CACrB,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,IAAI,CAAC,SAAS;IACtD,MAAM,CAAC,SAAS,MAAM;IACtB,GAAG,MAAM,CAAC,SAAS,MAAM,EAAE;CAC5B,GACG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GACzB,EAAE,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACpD,GAAG,IAAI,EAAE,CAAC,GACT,SAAS,CAAC,CAAC,CAAC,CAyEd"}
|
package/dist/createView.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INSPECT_MARKER } from "./inspect";
|
|
1
|
+
import { INSPECT_MARKER, INSPECTOR_ENABLED } from "./inspect";
|
|
2
2
|
/**
|
|
3
3
|
* Creates a view that merges multiple objects (reactive or not) into a single object while
|
|
4
4
|
* maintaining reactivity through getters. Properties from later arguments override earlier ones.
|
|
@@ -45,9 +45,12 @@ import { INSPECT_MARKER } from "./inspect";
|
|
|
45
45
|
export function createView(...args) {
|
|
46
46
|
const result = {};
|
|
47
47
|
const seen = new Set();
|
|
48
|
-
let
|
|
48
|
+
let notifyInspectorRef = {};
|
|
49
49
|
for (let i = args.length - 1; i >= 0; i--) {
|
|
50
50
|
const src = args[i];
|
|
51
|
+
if (INSPECTOR_ENABLED && src[INSPECT_MARKER]) {
|
|
52
|
+
src[INSPECT_MARKER] = notifyInspectorRef;
|
|
53
|
+
}
|
|
51
54
|
// mimic Object.assign: only enumerable own property keys
|
|
52
55
|
for (const key of Reflect.ownKeys(src)) {
|
|
53
56
|
if (seen.has(key))
|
|
@@ -61,20 +64,22 @@ export function createView(...args) {
|
|
|
61
64
|
configurable: true,
|
|
62
65
|
get: () => {
|
|
63
66
|
const value = src[key];
|
|
64
|
-
if (!
|
|
67
|
+
if (!INSPECTOR_ENABLED || !notifyInspectorRef.current) {
|
|
65
68
|
return value;
|
|
66
69
|
}
|
|
67
70
|
if (value?.[INSPECT_MARKER]) {
|
|
68
71
|
value[INSPECT_MARKER] = {
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
current: {
|
|
73
|
+
notify: notifyInspectorRef.current.notify,
|
|
74
|
+
path: notifyInspectorRef.current.path.concat(key),
|
|
75
|
+
},
|
|
71
76
|
};
|
|
72
77
|
}
|
|
73
78
|
else if (typeof value === "function") {
|
|
74
79
|
return (...params) => {
|
|
75
|
-
|
|
80
|
+
notifyInspectorRef.current.notify({
|
|
76
81
|
type: "action",
|
|
77
|
-
path:
|
|
82
|
+
path: notifyInspectorRef.current.path.concat(key),
|
|
78
83
|
params,
|
|
79
84
|
});
|
|
80
85
|
return value(...params);
|
|
@@ -86,15 +91,21 @@ export function createView(...args) {
|
|
|
86
91
|
seen.add(key);
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
if (INSPECTOR_ENABLED) {
|
|
95
|
+
Object.defineProperty(result, INSPECT_MARKER, {
|
|
96
|
+
enumerable: false,
|
|
97
|
+
configurable: false,
|
|
98
|
+
get() {
|
|
99
|
+
return !notifyInspectorRef.current;
|
|
100
|
+
},
|
|
101
|
+
set: (value) => {
|
|
102
|
+
Object.defineProperty(notifyInspectorRef, "current", {
|
|
103
|
+
get() {
|
|
104
|
+
return value.current;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
99
110
|
return result;
|
|
100
111
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { render } from "./render";
|
|
2
|
-
export { createCleanup, createMountEffect,
|
|
2
|
+
export { createCleanup, createMountEffect, RaskStatefulComponent, RaskStatelessComponent, } from "./component";
|
|
3
3
|
export { createContext } from "./createContext";
|
|
4
4
|
export { createState } from "./createState";
|
|
5
5
|
export { createTask } from "./createTask";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { render } from "./render";
|
|
2
|
-
export { createCleanup, createMountEffect,
|
|
2
|
+
export { createCleanup, createMountEffect, RaskStatefulComponent, RaskStatelessComponent, } from "./component";
|
|
3
3
|
export { createContext } from "./createContext";
|
|
4
4
|
export { createState } from "./createState";
|
|
5
5
|
export { createTask } from "./createTask";
|
package/dist/inspect.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const INSPECT_MARKER: unique symbol;
|
|
2
|
+
export declare const INSPECTOR_ENABLED: boolean;
|
|
2
3
|
export type InspectEvent = {
|
|
3
4
|
type: "mutation";
|
|
4
5
|
path: string[];
|
|
@@ -7,7 +8,18 @@ export type InspectEvent = {
|
|
|
7
8
|
type: "action";
|
|
8
9
|
path: string[];
|
|
9
10
|
params: any[];
|
|
11
|
+
} | {
|
|
12
|
+
type: "computed";
|
|
13
|
+
path: string[];
|
|
14
|
+
isDirty: boolean;
|
|
15
|
+
value: any;
|
|
10
16
|
};
|
|
11
17
|
export type InspectorCallback = (event: InspectEvent) => void;
|
|
18
|
+
export type InspectorRef = {
|
|
19
|
+
current?: {
|
|
20
|
+
notify: InspectorCallback;
|
|
21
|
+
path: string[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
12
24
|
export declare function inspect(root: any, cb: InspectorCallback): void;
|
|
13
25
|
//# sourceMappingURL=inspect.d.ts.map
|
package/dist/inspect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,eAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,eAAoB,CAAC;AAGhD,eAAO,MAAM,iBAAiB,SAAsB,CAAC;AAErD,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,GAAG,CAAC;CACZ,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,GAAG,EAAE,CAAC;CACf,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC;CACZ,CAAC;AAEN,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACzD,CAAC;AAEF,wBAAgB,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,iBAAiB,QAWvD"}
|
package/dist/inspect.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export const INSPECT_MARKER = Symbol("INSPECT");
|
|
2
|
+
// Flag to check if inspector is enabled (only in development)
|
|
3
|
+
export const INSPECTOR_ENABLED = import.meta.env.DEV;
|
|
2
4
|
export function inspect(root, cb) {
|
|
5
|
+
if (!INSPECTOR_ENABLED) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
3
8
|
root[INSPECT_MARKER] = {
|
|
4
|
-
|
|
5
|
-
|
|
9
|
+
current: {
|
|
10
|
+
notify: cb,
|
|
11
|
+
path: [],
|
|
12
|
+
},
|
|
6
13
|
};
|
|
7
14
|
}
|
package/package.json
CHANGED
package/swc-plugin/src/lib.rs
CHANGED
|
@@ -15,14 +15,16 @@ pub struct Config {
|
|
|
15
15
|
|
|
16
16
|
pub struct RaskComponentTransform {
|
|
17
17
|
config: Config,
|
|
18
|
-
|
|
18
|
+
import_rask_stateful_component: Option<Ident>,
|
|
19
|
+
import_rask_stateless_component: Option<Ident>,
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
impl RaskComponentTransform {
|
|
22
23
|
fn new(config: Config) -> Self {
|
|
23
24
|
RaskComponentTransform {
|
|
24
25
|
config,
|
|
25
|
-
|
|
26
|
+
import_rask_stateful_component: None,
|
|
27
|
+
import_rask_stateless_component: None,
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -100,7 +102,27 @@ impl RaskComponentTransform {
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
/// Check if a function body returns
|
|
105
|
+
/// Check if a function body directly returns VNode calls (stateless component)
|
|
106
|
+
fn is_stateless_component(&self, func: &Function) -> bool {
|
|
107
|
+
if let Some(body) = &func.body {
|
|
108
|
+
for stmt in &body.stmts {
|
|
109
|
+
if let Stmt::Return(ret_stmt) = stmt {
|
|
110
|
+
if let Some(ret_arg) = &ret_stmt.arg {
|
|
111
|
+
// Check if directly returning VNode (not arrow function)
|
|
112
|
+
if self.has_vnode_call(ret_arg) {
|
|
113
|
+
// Make sure it's NOT an arrow function
|
|
114
|
+
if !matches!(&**ret_arg, Expr::Arrow(_)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Check if a function body returns an arrow function with VNode calls (stateful component)
|
|
104
126
|
fn is_rask_component(&self, func: &Function) -> bool {
|
|
105
127
|
if let Some(body) = &func.body {
|
|
106
128
|
for stmt in &body.stmts {
|
|
@@ -135,14 +157,14 @@ impl RaskComponentTransform {
|
|
|
135
157
|
false
|
|
136
158
|
}
|
|
137
159
|
|
|
138
|
-
/// Transform a function declaration to a
|
|
139
|
-
fn
|
|
140
|
-
// Ensure we have the
|
|
141
|
-
if self.
|
|
142
|
-
self.
|
|
160
|
+
/// Transform a function declaration to a RaskStatefulComponent class
|
|
161
|
+
fn transform_to_stateful_class(&mut self, name: Ident, func: Function) -> Decl {
|
|
162
|
+
// Ensure we have the RaskStatefulComponent import
|
|
163
|
+
if self.import_rask_stateful_component.is_none() {
|
|
164
|
+
self.import_rask_stateful_component = Some(private_ident!("RaskStatefulComponent"));
|
|
143
165
|
}
|
|
144
166
|
|
|
145
|
-
let super_class_ident = self.
|
|
167
|
+
let super_class_ident = self.import_rask_stateful_component.as_ref().unwrap().clone();
|
|
146
168
|
|
|
147
169
|
// Create the class property: setup = function name() { ... }
|
|
148
170
|
let setup_prop = ClassMember::ClassProp(ClassProp {
|
|
@@ -181,6 +203,52 @@ impl RaskComponentTransform {
|
|
|
181
203
|
})
|
|
182
204
|
}
|
|
183
205
|
|
|
206
|
+
/// Transform a function declaration to a RaskStatelessComponent class
|
|
207
|
+
fn transform_to_stateless_class(&mut self, name: Ident, func: Function) -> Decl {
|
|
208
|
+
// Ensure we have the RaskStatelessComponent import
|
|
209
|
+
if self.import_rask_stateless_component.is_none() {
|
|
210
|
+
self.import_rask_stateless_component = Some(private_ident!("RaskStatelessComponent"));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let super_class_ident = self.import_rask_stateless_component.as_ref().unwrap().clone();
|
|
214
|
+
|
|
215
|
+
// Create the class property: renderFn = function name() { ... }
|
|
216
|
+
let render_prop = ClassMember::ClassProp(ClassProp {
|
|
217
|
+
span: Default::default(),
|
|
218
|
+
key: PropName::Ident(quote_ident!("renderFn").into()),
|
|
219
|
+
value: Some(Box::new(Expr::Fn(FnExpr {
|
|
220
|
+
ident: Some(name.clone()),
|
|
221
|
+
function: Box::new(func),
|
|
222
|
+
}))),
|
|
223
|
+
type_ann: None,
|
|
224
|
+
is_static: false,
|
|
225
|
+
decorators: vec![],
|
|
226
|
+
accessibility: None,
|
|
227
|
+
is_abstract: false,
|
|
228
|
+
is_optional: false,
|
|
229
|
+
is_override: false,
|
|
230
|
+
readonly: false,
|
|
231
|
+
declare: false,
|
|
232
|
+
definite: false,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
Decl::Class(ClassDecl {
|
|
236
|
+
ident: name,
|
|
237
|
+
declare: false,
|
|
238
|
+
class: Box::new(Class {
|
|
239
|
+
span: Default::default(),
|
|
240
|
+
ctxt: Default::default(),
|
|
241
|
+
decorators: vec![],
|
|
242
|
+
body: vec![render_prop],
|
|
243
|
+
super_class: Some(Box::new(Expr::Ident(super_class_ident))),
|
|
244
|
+
is_abstract: false,
|
|
245
|
+
type_params: None,
|
|
246
|
+
super_type_params: None,
|
|
247
|
+
implements: vec![],
|
|
248
|
+
}),
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
184
252
|
/// Rewrite imports from "inferno" to the configured import source
|
|
185
253
|
fn rewrite_inferno_imports(&mut self, module: &mut Module) {
|
|
186
254
|
let import_source = self
|
|
@@ -204,12 +272,8 @@ impl RaskComponentTransform {
|
|
|
204
272
|
}
|
|
205
273
|
}
|
|
206
274
|
|
|
207
|
-
/// Inject the
|
|
275
|
+
/// Inject the RaskStatefulComponent and/or RaskStatelessComponent imports at the top of the module
|
|
208
276
|
fn inject_runtime(&mut self, module: &mut Module) {
|
|
209
|
-
if self.import_rask_component.is_none() {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
277
|
let import_source = self
|
|
214
278
|
.config
|
|
215
279
|
.import_source
|
|
@@ -217,47 +281,93 @@ impl RaskComponentTransform {
|
|
|
217
281
|
.map(|s| s.as_str())
|
|
218
282
|
.unwrap_or("rask-ui");
|
|
219
283
|
|
|
220
|
-
let
|
|
284
|
+
let mut specifiers = vec![];
|
|
285
|
+
|
|
286
|
+
// Add RaskStatefulComponent if needed
|
|
287
|
+
if let Some(stateful_ident) = &self.import_rask_stateful_component {
|
|
288
|
+
// Check if import already exists
|
|
289
|
+
let mut exists = false;
|
|
290
|
+
for item in &module.body {
|
|
291
|
+
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item {
|
|
292
|
+
if &*import.src.value == import_source {
|
|
293
|
+
for spec in &import.specifiers {
|
|
294
|
+
if let ImportSpecifier::Named(named) = spec {
|
|
295
|
+
if let Some(ModuleExportName::Ident(imported)) = &named.imported {
|
|
296
|
+
if &*imported.sym == "RaskStatefulComponent" {
|
|
297
|
+
exists = true;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
} else if &*named.local.sym == "RaskStatefulComponent" {
|
|
301
|
+
exists = true;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
221
309
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
310
|
+
if !exists {
|
|
311
|
+
specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier {
|
|
312
|
+
span: Default::default(),
|
|
313
|
+
local: stateful_ident.clone(),
|
|
314
|
+
imported: Some(ModuleExportName::Ident(quote_ident!("RaskStatefulComponent").into())),
|
|
315
|
+
is_type_only: false,
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Add RaskStatelessComponent if needed
|
|
321
|
+
if let Some(stateless_ident) = &self.import_rask_stateless_component {
|
|
322
|
+
// Check if import already exists
|
|
323
|
+
let mut exists = false;
|
|
324
|
+
for item in &module.body {
|
|
325
|
+
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item {
|
|
326
|
+
if &*import.src.value == import_source {
|
|
327
|
+
for spec in &import.specifiers {
|
|
328
|
+
if let ImportSpecifier::Named(named) = spec {
|
|
329
|
+
if let Some(ModuleExportName::Ident(imported)) = &named.imported {
|
|
330
|
+
if &*imported.sym == "RaskStatelessComponent" {
|
|
331
|
+
exists = true;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
} else if &*named.local.sym == "RaskStatelessComponent" {
|
|
335
|
+
exists = true;
|
|
336
|
+
break;
|
|
231
337
|
}
|
|
232
|
-
} else if &*named.local.sym == "RaskComponent" {
|
|
233
|
-
return; // Import already exists
|
|
234
338
|
}
|
|
235
339
|
}
|
|
236
340
|
}
|
|
237
341
|
}
|
|
238
342
|
}
|
|
343
|
+
|
|
344
|
+
if !exists {
|
|
345
|
+
specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier {
|
|
346
|
+
span: Default::default(),
|
|
347
|
+
local: stateless_ident.clone(),
|
|
348
|
+
imported: Some(ModuleExportName::Ident(quote_ident!("RaskStatelessComponent").into())),
|
|
349
|
+
is_type_only: false,
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
239
352
|
}
|
|
240
353
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
|
|
245
|
-
span: Default::default(),
|
|
246
|
-
local: rask_component_ident.clone(),
|
|
247
|
-
imported: Some(ModuleExportName::Ident(quote_ident!("RaskComponent").into())),
|
|
248
|
-
is_type_only: false,
|
|
249
|
-
})],
|
|
250
|
-
src: Box::new(Str {
|
|
354
|
+
// Only create import if we have specifiers to add
|
|
355
|
+
if !specifiers.is_empty() {
|
|
356
|
+
let import = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
|
251
357
|
span: Default::default(),
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
358
|
+
specifiers,
|
|
359
|
+
src: Box::new(Str {
|
|
360
|
+
span: Default::default(),
|
|
361
|
+
value: Wtf8Atom::from(import_source),
|
|
362
|
+
raw: None,
|
|
363
|
+
}),
|
|
364
|
+
type_only: false,
|
|
365
|
+
with: None,
|
|
366
|
+
phase: Default::default(),
|
|
367
|
+
}));
|
|
368
|
+
|
|
369
|
+
module.body.insert(0, import);
|
|
370
|
+
}
|
|
261
371
|
}
|
|
262
372
|
}
|
|
263
373
|
|
|
@@ -278,11 +388,19 @@ impl VisitMut for RaskComponentTransform {
|
|
|
278
388
|
fn visit_mut_module_item(&mut self, item: &mut ModuleItem) {
|
|
279
389
|
match item {
|
|
280
390
|
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) => {
|
|
281
|
-
// Check
|
|
391
|
+
// Check for stateful component first (returns arrow function)
|
|
282
392
|
if self.is_rask_component(&fn_decl.function) {
|
|
283
393
|
let name = fn_decl.ident.clone();
|
|
284
394
|
let func = (*fn_decl.function).clone();
|
|
285
|
-
let class_decl = self.
|
|
395
|
+
let class_decl = self.transform_to_stateful_class(name, func);
|
|
396
|
+
*item = ModuleItem::Stmt(Stmt::Decl(class_decl));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
// Then check for stateless component (directly returns VNode)
|
|
400
|
+
else if self.is_stateless_component(&fn_decl.function) {
|
|
401
|
+
let name = fn_decl.ident.clone();
|
|
402
|
+
let func = (*fn_decl.function).clone();
|
|
403
|
+
let class_decl = self.transform_to_stateless_class(name, func);
|
|
286
404
|
*item = ModuleItem::Stmt(Stmt::Decl(class_decl));
|
|
287
405
|
return;
|
|
288
406
|
}
|
|
@@ -295,16 +413,27 @@ impl VisitMut for RaskComponentTransform {
|
|
|
295
413
|
// but transform the pattern internally if needed
|
|
296
414
|
// For now, we'll skip transforming default exports
|
|
297
415
|
// as they need special handling
|
|
416
|
+
} else if self.is_stateless_component(&fn_expr.function) {
|
|
417
|
+
// Same for stateless default exports
|
|
298
418
|
}
|
|
299
419
|
}
|
|
300
420
|
}
|
|
301
421
|
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
|
|
302
422
|
// Handle: export function MyComponent() { return () => <div /> }
|
|
303
423
|
if let Decl::Fn(fn_decl) = &mut export.decl {
|
|
424
|
+
// Check for stateful component first
|
|
304
425
|
if self.is_rask_component(&fn_decl.function) {
|
|
305
426
|
let name = fn_decl.ident.clone();
|
|
306
427
|
let func = (*fn_decl.function).clone();
|
|
307
|
-
let class_decl = self.
|
|
428
|
+
let class_decl = self.transform_to_stateful_class(name, func);
|
|
429
|
+
export.decl = class_decl;
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// Then check for stateless component
|
|
433
|
+
else if self.is_stateless_component(&fn_decl.function) {
|
|
434
|
+
let name = fn_decl.ident.clone();
|
|
435
|
+
let func = (*fn_decl.function).clone();
|
|
436
|
+
let class_decl = self.transform_to_stateless_class(name, func);
|
|
308
437
|
export.decl = class_decl;
|
|
309
438
|
return;
|
|
310
439
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|