experimental: Allow json in template literals (#3886)

## Description
closes #3872


- [x] - Builder support
- [x] - Publish support

## Steps for reproduction

Example

https://add-json.staging.webstudio.is/builder/8160e16d-5ab4-4249-86de-0c98a64526a3

https://test-json-ki9yj.wstd.work/

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
Ivan Starkov authored and GitHub committed 2024-08-11 11:50:18 +03:00
1 parent 403d7b2328
commit 13da170ef4
38 files changed
+888 -173

No files matched your search

@@ -0,0 +1,15 @@
import { expect, test } from "@jest/globals";
import { evaluateExpressionWithinScope } from "./binding-popover";
import { encodeDataSourceVariable } from "@webstudio-is/sdk";
test("evaluateExpressionWithinScope works", () => {
const variableName = "jsonVariable";
const encVariableName = encodeDataSourceVariable(variableName);
const variableValue = 1;
expect(
evaluateExpressionWithinScope(`${encVariableName} + ${encVariableName}`, {
[encVariableName]: variableValue,
})
).toEqual(2);
});
@@ -46,23 +46,21 @@ import {
type EditorApi,
} from "./expression-editor";
import { useSideOffset } from "./floating-panel";
import { $dataSourceVariables } from "~/shared/nano-states";
import { $dataSourceVariables, computeExpression } from "~/shared/nano-states";
export const evaluateExpressionWithinScope = (
expression: string,
scope: Record<string, unknown>
) => {
let expressionWithScope = "";
const variables = new Map<string, unknown>();
for (const [name, value] of Object.entries(scope)) {
expressionWithScope += `const ${name} = ${JSON.stringify(value)};\n`;
}
expressionWithScope += `return (${expression})`;
try {
const fn = new Function(expressionWithScope);
return fn();
} catch {
//
const decodedName = decodeDataSourceVariable(name);
if (decodedName) {
variables.set(decodedName, value);
}
}
return computeExpression(expression, variables);
};
const BindingPanel = ({
@@ -447,6 +445,7 @@ export const BindingPopover = ({
value,
scope
);
onRemove(evaluatedValue);
preventedClosing.current = false;
hasUnsavedChange.current = false;
@@ -2,7 +2,7 @@ import { beforeEach, expect, test } from "@jest/globals";
import { cleanStores } from "nanostores";
import { createDefaultPages } from "@webstudio-is/project-build";
import { setEnv } from "@webstudio-is/feature-flags";
import type { Instance } from "@webstudio-is/sdk";
import { encodeDataSourceVariable, type Instance } from "@webstudio-is/sdk";
import {
collectionComponent,
textContentAttribute,
@@ -993,3 +993,20 @@ test("prefill default system variable value", () => {
test("compute expression when invalid syntax", () => {
expect(computeExpression("https://github.com", new Map())).toEqual(undefined);
});
test("compute literal expression when variable is json object", () => {
const variableName = "jsonVariable";
const encVariableName = encodeDataSourceVariable(variableName);
const jsonObject = { hello: "world", subObject: { world: "hello" } };
const variables = new Map([[variableName, jsonObject]]);
const expression = "`${" + encVariableName + "}`";
expect(computeExpression(expression, variables)).toEqual(
JSON.stringify(jsonObject)
);
const subObjectExpression = "`${" + encVariableName + ".subObject}`";
expect(computeExpression(subObjectExpression, variables)).toEqual(
JSON.stringify(jsonObject.subObject)
);
});
+11 -1
View File
@@ -9,8 +9,10 @@ import type {
ImageAsset,
} from "@webstudio-is/sdk";
import {
createJsonStringifyProxy,
decodeDataSourceVariable,
encodeDataSourceVariable,
isPlainObject,
transpileExpression,
} from "@webstudio-is/sdk";
import {
@@ -223,7 +225,15 @@ export const computeExpression = (
code += `let ${identifier} = _variables.get("${id}");\n`;
}
code += `return (${transpiled})`;
const result = new Function("_variables", code)(variables);
const proxiedVariables = new Map(
[...variables.entries()].map(([key, value]) => [
key,
isPlainObject(value) ? createJsonStringifyProxy(value) : value,
])
);
const result = new Function("_variables", code)(proxiedVariables);
return result;
} catch {
// empty block