@webstudio-is/react-sdk 0.76.0 → 0.77.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/src/expression.ts CHANGED
@@ -111,15 +111,42 @@ export const validateExpression = (
111
111
  return generateCode(expression, true, transformIdentifier);
112
112
  };
113
113
 
114
- export const executeExpressions = (
115
- variables: Map<string, unknown>,
114
+ const sortTopologically = (
115
+ list: Set<string>,
116
+ depsById: Map<string, Set<string>>,
117
+ explored = new Set<string>(),
118
+ sorted: string[] = []
119
+ ) => {
120
+ for (const id of list) {
121
+ if (explored.has(id)) {
122
+ continue;
123
+ }
124
+ explored.add(id);
125
+ const deps = depsById.get(id);
126
+ if (deps) {
127
+ sortTopologically(deps, depsById, explored, sorted);
128
+ }
129
+ sorted.push(id);
130
+ }
131
+ return sorted;
132
+ };
133
+
134
+ /**
135
+ * Generates a function body expecting map as _variables argument
136
+ * and outputing map of results
137
+ */
138
+ export const generateExpressionsComputation = (
139
+ variables: Set<string>,
116
140
  expressions: Map<string, string>
117
141
  ) => {
118
142
  const depsById = new Map<string, Set<string>>();
119
143
  for (const [id, code] of expressions) {
120
144
  const deps = new Set<string>();
121
145
  validateExpression(code, (identifier) => {
122
- if (variables.has(identifier) || expressions.has(identifier)) {
146
+ if (variables.has(identifier)) {
147
+ return identifier;
148
+ }
149
+ if (expressions.has(identifier)) {
123
150
  deps.add(identifier);
124
151
  return identifier;
125
152
  }
@@ -128,38 +155,45 @@ export const executeExpressions = (
128
155
  depsById.set(id, deps);
129
156
  }
130
157
 
131
- // sort topologically
132
- const sortedExpressions = Array.from(expressions.keys()).sort(
133
- (left, right) => {
134
- if (depsById.get(left)?.has(right)) {
135
- return 1;
136
- }
137
- if (depsById.get(right)?.has(left)) {
138
- return -1;
139
- }
140
- return 0;
141
- }
158
+ const sortedExpressions = sortTopologically(
159
+ new Set(expressions.keys()),
160
+ depsById
142
161
  );
143
162
 
144
- // execute chain of expressions
145
- let header = "";
146
- for (const [id, value] of variables) {
147
- header += `const ${id} = ${JSON.stringify(value)};\n`;
148
- }
163
+ // generate code computing all expressions
164
+ let generatedCode = "";
149
165
 
150
- const values = new Map<string, unknown>();
166
+ for (const id of variables) {
167
+ generatedCode += `const ${id} = _variables.get('${id}');\n`;
168
+ }
151
169
 
152
170
  for (const id of sortedExpressions) {
153
171
  const code = expressions.get(id);
154
172
  if (code === undefined) {
155
173
  continue;
156
174
  }
157
- const executeFn = new Function(`${header}\nreturn (${code});`);
158
- const value = executeFn();
159
- header += `const ${id} = ${JSON.stringify(value)};\n`;
160
- values.set(id, value);
175
+ generatedCode += `const ${id} = (${code});\n`;
161
176
  }
162
177
 
178
+ generatedCode += `return new Map([\n`;
179
+ for (const id of sortedExpressions) {
180
+ generatedCode += ` ['${id}', ${id}],\n`;
181
+ }
182
+ generatedCode += `]);`;
183
+
184
+ return generatedCode;
185
+ };
186
+
187
+ export const executeExpressions = (
188
+ variables: Map<string, unknown>,
189
+ expressions: Map<string, string>
190
+ ) => {
191
+ const generatedCode = generateExpressionsComputation(
192
+ new Set(variables.keys()),
193
+ expressions
194
+ );
195
+ const executeFn = new Function("_variables", generatedCode);
196
+ const values = executeFn(variables) as Map<string, unknown>;
163
197
  return values;
164
198
  };
165
199
 
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  export { type Params, ReactSdkContext } from "./context";
23
23
  export {
24
24
  validateExpression,
25
+ generateExpressionsComputation,
25
26
  executeExpressions,
26
27
  encodeDataSourceVariable,
27
28
  decodeDataSourceVariable,
package/src/tree/root.ts CHANGED
@@ -12,45 +12,6 @@ import { WebstudioComponent } from "./webstudio-component";
12
12
  import { getPropsByInstanceId } from "../props";
13
13
  import type { Components } from "../components/components-utils";
14
14
  import type { Params } from "../context";
15
- import {
16
- executeExpressions,
17
- encodeDataSourceVariable,
18
- decodeDataSourceVariable,
19
- } from "../expression";
20
-
21
- const computeExpressions = (
22
- dataSources: [DataSource["id"], DataSource][],
23
- dataSourceValues: Map<DataSource["id"], unknown>
24
- ) => {
25
- const outputValues = new Map<DataSource["id"], unknown>();
26
- const variables = new Map<string, unknown>();
27
- const expressions = new Map<string, string>();
28
- for (const [dataSourceId, dataSource] of dataSources) {
29
- const name = encodeDataSourceVariable(dataSourceId);
30
- if (dataSource.type === "variable") {
31
- const value =
32
- dataSourceValues.get(dataSourceId) ?? dataSource.value.value;
33
- variables.set(name, value);
34
- outputValues.set(dataSourceId, value);
35
- }
36
- if (dataSource.type === "expression") {
37
- expressions.set(name, dataSource.code);
38
- }
39
- }
40
- try {
41
- const outputVariables = executeExpressions(variables, expressions);
42
- for (const [name, value] of outputVariables) {
43
- const id = decodeDataSourceVariable(name);
44
- if (id !== undefined) {
45
- outputValues.set(id, value);
46
- }
47
- }
48
- } catch (error) {
49
- // eslint-disable-next-line no-console
50
- console.error(error);
51
- }
52
- return outputValues;
53
- };
54
15
 
55
16
  export type Data = {
56
17
  page: Page;
@@ -64,39 +25,58 @@ export type RootPropsData = Omit<Data, "build"> & {
64
25
  build: Pick<Data["build"], "instances" | "props" | "dataSources">;
65
26
  };
66
27
 
28
+ type DataSourceValues = Map<DataSource["id"], unknown>;
29
+
67
30
  type RootProps = {
68
31
  data: RootPropsData;
32
+ computeExpressions: (values: DataSourceValues) => DataSourceValues;
69
33
  Component?: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
70
34
  components: Components;
71
35
  };
72
36
 
73
37
  export const InstanceRoot = ({
74
38
  data,
39
+ computeExpressions,
75
40
  Component,
76
41
  components,
77
42
  }: RootProps): JSX.Element | null => {
78
43
  const dataSourceVariablesStoreRef = useRef<
79
- undefined | WritableAtom<Map<DataSource["id"], unknown>>
44
+ undefined | WritableAtom<DataSourceValues>
80
45
  >(undefined);
81
- // initialize store with default data source values
82
46
  if (dataSourceVariablesStoreRef.current === undefined) {
83
- const dataSourceVariables = new Map<DataSource["id"], unknown>();
84
- for (const [dataSourceId, dataSource] of data.build.dataSources) {
85
- dataSourceVariables.set(dataSourceId, dataSource);
86
- }
87
- dataSourceVariablesStoreRef.current = atom(dataSourceVariables);
47
+ dataSourceVariablesStoreRef.current = atom(new Map());
88
48
  }
89
49
  const dataSourceVariablesStore = dataSourceVariablesStoreRef.current;
90
50
 
91
51
  const dataSourceValuesStoreRef = useRef<
92
- undefined | ReadableAtom<Map<DataSource["id"], unknown>>
52
+ undefined | ReadableAtom<DataSourceValues>
93
53
  >(undefined);
94
- // initialize store with default data source values
95
54
  if (dataSourceValuesStoreRef.current === undefined) {
96
55
  dataSourceValuesStoreRef.current = computed(
97
56
  dataSourceVariablesStore,
98
57
  (dataSourceVariables) => {
99
- return computeExpressions(data.build.dataSources, dataSourceVariables);
58
+ // set vriables with defaults
59
+ const dataSourceValues: DataSourceValues = new Map();
60
+ for (const [dataSourceId, dataSource] of data.build.dataSources) {
61
+ if (dataSource.type === "variable") {
62
+ const value =
63
+ dataSourceVariables.get(dataSourceId) ?? dataSource.value.value;
64
+ dataSourceValues.set(dataSourceId, value);
65
+ }
66
+ }
67
+
68
+ // set expression values
69
+ try {
70
+ const result = computeExpressions(dataSourceValues);
71
+ for (const [id, value] of result) {
72
+ dataSourceValues.set(id, value);
73
+ }
74
+ } catch (error) {
75
+ // eslint-disable-next-line no-console
76
+ console.error(error);
77
+ }
78
+
79
+ return dataSourceValues;
100
80
  }
101
81
  );
102
82
  }