diff --git a/apps/builder/app/builder/features/address-bar.stories.tsx b/apps/builder/app/builder/features/address-bar.stories.tsx index 23e8cc3bdad2..601bd84cef29 100644 --- a/apps/builder/app/builder/features/address-bar.stories.tsx +++ b/apps/builder/app/builder/features/address-bar.stories.tsx @@ -61,7 +61,7 @@ $pages.set({ const $selectedPageSystem = computed( [$selectedPage, $dataSourceVariables], (selectedPage, dataSourceVariables) => { - if (selectedPage === undefined) { + if (selectedPage?.systemDataSourceId === undefined) { return {}; } return dataSourceVariables.get(selectedPage.systemDataSourceId); diff --git a/apps/builder/app/builder/features/address-bar.tsx b/apps/builder/app/builder/features/address-bar.tsx index 604d639be7a6..f186ff232a24 100644 --- a/apps/builder/app/builder/features/address-bar.tsx +++ b/apps/builder/app/builder/features/address-bar.tsx @@ -65,7 +65,7 @@ const $selectedPagePath = computed([$selectedPage, $pages], (page, pages) => { const $selectedPagePathParams = computed( [$selectedPageDefaultSystem, $selectedPage, $dataSourceVariables], (defaultSystem, selectedPage, dataSourceVariables) => { - if (selectedPage === undefined) { + if (selectedPage?.systemDataSourceId === undefined) { return defaultSystem.params; } const system = dataSourceVariables.get(selectedPage.systemDataSourceId) as diff --git a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx index 11d7b8ec0f7f..e2e92a7f42ac 100644 --- a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx +++ b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx @@ -375,7 +375,7 @@ const $hiddenDataSourceIds = computed( dataSourceIds.add(dataSource.id); } } - if (page && isFeatureEnabled("filters")) { + if (page?.systemDataSourceId && isFeatureEnabled("filters")) { dataSourceIds.delete(page.systemDataSourceId); } return dataSourceIds; diff --git a/apps/builder/app/canvas/interceptor.ts b/apps/builder/app/canvas/interceptor.ts index bb3f8a49fefe..9fd2ff2ed3a2 100644 --- a/apps/builder/app/canvas/interceptor.ts +++ b/apps/builder/app/canvas/interceptor.ts @@ -27,7 +27,7 @@ const getSelectedPagePathname = () => { const pages = $pages.get(); const page = $selectedPage.get(); const dataSourceVariables = $dataSourceVariables.get(); - if (page && pages) { + if (page?.systemDataSourceId && pages) { const system = dataSourceVariables.get(page.systemDataSourceId) as | undefined | System; diff --git a/apps/builder/app/shared/nano-states/variables.ts b/apps/builder/app/shared/nano-states/variables.ts index 4e9632c51f32..d78477022189 100644 --- a/apps/builder/app/shared/nano-states/variables.ts +++ b/apps/builder/app/shared/nano-states/variables.ts @@ -60,6 +60,9 @@ export const mergeSystem = (left: System, right?: System): System => { }; export const updateSystem = (page: Page, update: Partial) => { + if (page.systemDataSourceId === undefined) { + return; + } const dataSourceVariables = new Map($dataSourceVariables.get()); const system = dataSourceVariables.get(page.systemDataSourceId) as | undefined diff --git a/apps/builder/app/shared/page-utils.ts b/apps/builder/app/shared/page-utils.ts index d65cbfd47d72..2095c1c1e866 100644 --- a/apps/builder/app/shared/page-utils.ts +++ b/apps/builder/app/shared/page-utils.ts @@ -118,7 +118,8 @@ export const insertPageCopyMutable = ({ const newRootInstanceId = newInstanceIds.get(page.rootInstanceId) ?? page.rootInstanceId; const newSystemDataSourceId = - newDataSourceIds.get(page.systemDataSourceId) ?? page.systemDataSourceId; + newDataSourceIds.get(page.systemDataSourceId ?? "") ?? + page.systemDataSourceId; const newPage: Page = { ...page, id: newPageId, diff --git a/fixtures/react-router-docker/app/__generated__/[another-page]._index.tsx b/fixtures/react-router-docker/app/__generated__/[another-page]._index.tsx index 0f11ceedef83..be01fcf76af3 100644 --- a/fixtures/react-router-docker/app/__generated__/[another-page]._index.tsx +++ b/fixtures/react-router-docker/app/__generated__/[another-page]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Another page"} diff --git a/fixtures/react-router-docker/app/__generated__/_index.tsx b/fixtures/react-router-docker/app/__generated__/_index.tsx index 9c073984cf91..0dee4dc4bbc6 100644 --- a/fixtures/react-router-docker/app/__generated__/_index.tsx +++ b/fixtures/react-router-docker/app/__generated__/_index.tsx @@ -37,7 +37,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Simple Project to test CLI"} diff --git a/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx index 0f11ceedef83..be01fcf76af3 100644 --- a/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx +++ b/fixtures/react-router-netlify/app/__generated__/[another-page]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Another page"} diff --git a/fixtures/react-router-netlify/app/__generated__/_index.tsx b/fixtures/react-router-netlify/app/__generated__/_index.tsx index 9c073984cf91..0dee4dc4bbc6 100644 --- a/fixtures/react-router-netlify/app/__generated__/_index.tsx +++ b/fixtures/react-router-netlify/app/__generated__/_index.tsx @@ -37,7 +37,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Simple Project to test CLI"} diff --git a/fixtures/react-router-vercel/app/__generated__/[another-page]._index.tsx b/fixtures/react-router-vercel/app/__generated__/[another-page]._index.tsx index 0f11ceedef83..be01fcf76af3 100644 --- a/fixtures/react-router-vercel/app/__generated__/[another-page]._index.tsx +++ b/fixtures/react-router-vercel/app/__generated__/[another-page]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Another page"} diff --git a/fixtures/react-router-vercel/app/__generated__/_index.tsx b/fixtures/react-router-vercel/app/__generated__/_index.tsx index 9c073984cf91..0dee4dc4bbc6 100644 --- a/fixtures/react-router-vercel/app/__generated__/_index.tsx +++ b/fixtures/react-router-vercel/app/__generated__/_index.tsx @@ -37,7 +37,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Simple Project to test CLI"} diff --git a/fixtures/ssg-netlify-by-project-id/app/__generated__/_index.tsx b/fixtures/ssg-netlify-by-project-id/app/__generated__/_index.tsx index 81c342dc370c..dd9671191290 100644 --- a/fixtures/ssg-netlify-by-project-id/app/__generated__/_index.tsx +++ b/fixtures/ssg-netlify-by-project-id/app/__generated__/_index.tsx @@ -22,7 +22,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"FIXTURE-CLIENT-DO-NOT-TOUCH"} diff --git a/fixtures/ssg/app/__generated__/[another-page]._index.tsx b/fixtures/ssg/app/__generated__/[another-page]._index.tsx index 61e1a518492d..dfaed7865a22 100644 --- a/fixtures/ssg/app/__generated__/[another-page]._index.tsx +++ b/fixtures/ssg/app/__generated__/[another-page]._index.tsx @@ -18,7 +18,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Another page"} diff --git a/fixtures/ssg/app/__generated__/_index.tsx b/fixtures/ssg/app/__generated__/_index.tsx index 0d718c54ffc1..0175191fe652 100644 --- a/fixtures/ssg/app/__generated__/_index.tsx +++ b/fixtures/ssg/app/__generated__/_index.tsx @@ -25,7 +25,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Simple Project to test CLI"} diff --git a/fixtures/webstudio-cloudflare-template/app/__generated__/[another-page]._index.tsx b/fixtures/webstudio-cloudflare-template/app/__generated__/[another-page]._index.tsx index 7946eb9573eb..4eaf37dd40c6 100644 --- a/fixtures/webstudio-cloudflare-template/app/__generated__/[another-page]._index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/__generated__/[another-page]._index.tsx @@ -16,7 +16,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Another page"} diff --git a/fixtures/webstudio-cloudflare-template/app/__generated__/_index.tsx b/fixtures/webstudio-cloudflare-template/app/__generated__/_index.tsx index 5866f7323d9a..54ff3fb3dbcc 100644 --- a/fixtures/webstudio-cloudflare-template/app/__generated__/_index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/__generated__/_index.tsx @@ -27,7 +27,7 @@ export const CustomCode = () => { return <>; }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Simple Project to test CLI"} diff --git a/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx index 8be898bf8a71..c046b8d0b655 100644 --- a/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( { +const Page = (_props: { system: any }) => { let [classVar, set$classVar] = useVariableState("varClass"); return ( diff --git a/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx b/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx index 915014f852e0..6bbe13ef889a 100644 --- a/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx @@ -30,7 +30,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( diff --git a/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx b/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx index a0deb6d33450..199ee50bba2a 100644 --- a/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx @@ -29,7 +29,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { let jsonResourceVariable = useResource("jsonResourceVariable_1"); let [jsonVar, set$jsonVar] = useVariableState({ hello: "world" }); return ( diff --git a/fixtures/webstudio-features/app/__generated__/[form]._index.tsx b/fixtures/webstudio-features/app/__generated__/[form]._index.tsx index 0f118431fae2..217d0371941f 100644 --- a/fixtures/webstudio-features/app/__generated__/[form]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[form]._index.tsx @@ -35,7 +35,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { let [formState, set$formState] = useVariableState("initial"); let [formState_1, set$formState_1] = useVariableState("initial"); return ( diff --git a/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx b/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx index f6e5a4b2dd0c..da3b068b14eb 100644 --- a/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx @@ -35,7 +35,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( diff --git a/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx b/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx index d08d9868422d..8c9b206ecad0 100644 --- a/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( diff --git a/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx b/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx index e417451e9877..ff94679534ad 100644 --- a/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx @@ -26,7 +26,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( {"Nested page"} diff --git a/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx b/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx index 9df7ab2ab5fd..e6ec472b2d7c 100644 --- a/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx @@ -37,7 +37,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { let [accordionValue, set$accordionValue] = useVariableState("0"); return ( diff --git a/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx b/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx index 5bc992e4e4df..8b5600c938e0 100644 --- a/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx @@ -29,7 +29,7 @@ export const pageFontAssets: FontAsset[] = []; export const pageBackgroundImageAssets: ImageAsset[] = []; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { let list = useResource("list_1"); return ( diff --git a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx index 6fe73ffb71e3..aef811376761 100644 --- a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx @@ -28,7 +28,8 @@ export const pageBackgroundImageAssets: ImageAsset[] = []; const Body = (props: any) => {props.children}; const Heading = (props: any) => null; -const Page = ({ system: system }: { system: any }) => { +const Page = (_props: { system: any }) => { + const system = _props.system; return ( { ); }; -const Page = ({}: { system: any }) => { +const Page = (_props: { system: any }) => { return ( diff --git a/packages/cli/src/prebuild.ts b/packages/cli/src/prebuild.ts index 3299770c41ab..48e85bf12e20 100644 --- a/packages/cli/src/prebuild.ts +++ b/packages/cli/src/prebuild.ts @@ -47,6 +47,7 @@ import { replaceFormActionsWithResources, isCoreComponent, coreMetas, + SYSTEM_VARIABLE_ID, } from "@webstudio-is/sdk"; import type { Data } from "@webstudio-is/http-client"; import { LOCAL_DATA_FILE } from "./config"; @@ -584,12 +585,19 @@ export const prebuild = async (options: { rootInstanceId, parameters: [ { - id: `system`, + id: `page-system`, instanceId: "", name: "system", type: "parameter", value: pageData.page.systemDataSourceId ?? "", }, + { + id: "global-system", + type: "parameter", + instanceId: "", + name: "system", + value: SYSTEM_VARIABLE_ID, + }, ], instances, props, diff --git a/packages/react-sdk/src/component-generator.test.tsx b/packages/react-sdk/src/component-generator.test.tsx index 9b319e718a92..7ae246d740a1 100644 --- a/packages/react-sdk/src/component-generator.test.tsx +++ b/packages/react-sdk/src/component-generator.test.tsx @@ -1,7 +1,11 @@ import ts from "typescript"; import { expect, test } from "vitest"; import stripIndent from "strip-indent"; -import { createScope, ROOT_INSTANCE_ID } from "@webstudio-is/sdk"; +import { + createScope, + ROOT_INSTANCE_ID, + SYSTEM_VARIABLE_ID, +} from "@webstudio-is/sdk"; import { $, ActionValue, @@ -647,7 +651,17 @@ test("avoid generating collection parameter variable as state", () => { ); }); -test("generate system variable when present", () => { +test("generate both page system and global system variables when present", () => { + const system = new Parameter("system"); + const data = renderData( + <$.Body + ws:id="body" + data-page={expression`${system}.params.slug`} + data-global={expression`$ws$system.params.slug`} + > + ); + expect(data.dataSources.size).toEqual(1); + const [pageSystemVariableId] = data.dataSources.keys(); expect( generateWebstudioComponent({ classesMap: new Map(), @@ -656,35 +670,32 @@ test("generate system variable when present", () => { rootInstanceId: "body", parameters: [ { - id: "pathSystemPropId", + id: "pathSystemPropId1", type: "parameter", instanceId: "", name: "system", - value: "systemId", + value: pageSystemVariableId, }, - ], - indexesWithinAncestors: new Map(), - ...renderData( - <$.Body - ws:id="body" - data-slug={expression`$ws$dataSource$systemId.params.slug`} - > - ), - dataSources: toMap([ { - id: "systemId", - scopeInstanceId: "body", + id: "pathSystemPropId2", type: "parameter", + instanceId: "", name: "system", + value: SYSTEM_VARIABLE_ID, }, - ]), + ], + indexesWithinAncestors: new Map(), + ...data, }) ).toEqual( validateJSX( clear(` - const Page = ({ system: system_1, }: { system: any; }) => { + const Page = (_props: { system: any; }) => { + const system_1 = _props.system; + const system_2 = _props.system; return + data-page={system_1?.params?.slug} + data-global={system_2?.params?.slug} /> } `) ) @@ -692,6 +703,12 @@ test("generate system variable when present", () => { }); test("generate resources loading", () => { + const dataVariable = new Variable("data", "data"); + const dataResource = new ResourceValue("data", { + url: expression`""`, + method: "get", + headers: [], + }); expect( generateWebstudioComponent({ classesMap: new Map(), @@ -703,26 +720,10 @@ test("generate resources loading", () => { ...renderData( <$.Body ws:id="body" - data-data={expression`$ws$dataSource$dataSourceDataId`} - data-resource={expression`$ws$dataSource$dataSourceResourceId`} + data-data={expression`${dataVariable}`} + data-resource={expression`${dataResource}`} > ), - dataSources: toMap([ - { - id: "dataSourceDataId", - scopeInstanceId: "body", - type: "variable", - name: "data", - value: { type: "json", value: "data" }, - }, - { - id: "dataSourceResourceId", - scopeInstanceId: "body", - type: "resource", - name: "data", - resourceId: "resourceId", - }, - ]), }) ).toEqual( validateJSX( @@ -740,6 +741,27 @@ test("generate resources loading", () => { }); test("avoid generating unused variables", () => { + const usedVariable = new Variable("Used Variable Name", "initial"); + const unusedVariable = new Variable("Unused Variable Name", "initial"); + const unusedParameter = new Parameter("Unused Parameter Name"); + const unusedResource = new ResourceValue("Unused Resource Name", { + url: expression`""`, + method: "get", + headers: [], + }); + const data = renderData( + <$.Body + ws:id="body" + data-used={expression`${usedVariable}`} + data-unused={expression`${unusedVariable} ${unusedParameter} ${unusedResource}`} + > + ); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ name: "data-used" }), + expect.objectContaining({ name: "data-unused" }), + ]); + // make variables unused + data.props.delete(Array.from(data.props.values())[1].id); expect( generateWebstudioComponent({ classesMap: new Map(), @@ -756,47 +778,13 @@ test("avoid generating unused variables", () => { }, ], indexesWithinAncestors: new Map(), - ...renderData( - <$.Body - ws:id="body" - data-data={expression`$ws$dataSource$usedVariableId`} - > - ), - dataSources: toMap([ - { - id: "usedVariableId", - scopeInstanceId: "body", - name: "Used Variable Name", - type: "variable", - value: { type: "string", value: "initial" }, - }, - { - id: "unusedVariableId", - scopeInstanceId: "body", - name: "Unused Variable Name", - type: "variable", - value: { type: "string", value: "initial" }, - }, - { - id: "unusedParameterId", - scopeInstanceId: "body", - name: "Unused Parameter Name", - type: "parameter", - }, - { - id: "unusedResourceVariableId", - scopeInstanceId: "body", - name: "Unused Resource Name", - type: "resource", - resourceId: "resourceId", - }, - ]), + ...data, }) ).toMatchInlineSnapshot(` -"const Page = ({ }: { system: any; }) => { +"const Page = (_props: { system: any; }) => { let [UsedVariableName, set$UsedVariableName] = useVariableState("initial") return +data-used={UsedVariableName} /> } " `); diff --git a/packages/react-sdk/src/component-generator.ts b/packages/react-sdk/src/component-generator.ts index 87a5899ec2a6..36871dd71577 100644 --- a/packages/react-sdk/src/component-generator.ts +++ b/packages/react-sdk/src/component-generator.ts @@ -431,21 +431,24 @@ export const generateWebstudioComponent = ({ }); let generatedProps = ""; + let generatedParameters = ""; + const uniqueParameters = new Set( + parameters.map((parameter) => parameter.name) + ); if (parameters.length > 0) { - let generatedPropsValue = "{ "; - let generatedPropsType = "{ "; + let generatedPropsType = ""; + for (const parameterName of uniqueParameters) { + generatedPropsType += `${parameterName}: any; `; + } + generatedProps = `_props: { ${generatedPropsType}}`; for (const parameter of parameters) { const dataSource = usedDataSources.get(parameter.value); // always generate type and avoid generating value when unused if (dataSource) { const valueName = scope.getName(dataSource.id, dataSource.name); - generatedPropsValue += `${parameter.name}: ${valueName}, `; + generatedParameters += `const ${valueName} = _props.${parameter.name};\n`; } - generatedPropsType += `${parameter.name}: any; `; } - generatedPropsValue += `}`; - generatedPropsType += `}`; - generatedProps = `${generatedPropsValue}: ${generatedPropsType}`; } let generatedDataSources = ""; @@ -475,6 +478,7 @@ export const generateWebstudioComponent = ({ let generatedComponent = ""; generatedComponent += `const ${name} = (${generatedProps}) => {\n`; + generatedComponent += `${generatedParameters}`; generatedComponent += `${generatedDataSources}`; generatedComponent += `return ${generatedJsx}`; generatedComponent += `}\n`; diff --git a/packages/react-sdk/src/props.test.ts b/packages/react-sdk/src/props.test.ts index cb04b22d9b3f..27bb83b54da9 100644 --- a/packages/react-sdk/src/props.test.ts +++ b/packages/react-sdk/src/props.test.ts @@ -10,7 +10,6 @@ const pagesBase: Pages = { name: "Home", title: "Home", rootInstanceId: "instance-1", - systemDataSourceId: "", meta: {}, }, pages: [], @@ -204,7 +203,6 @@ test("normalize page prop with path into string", () => { name: "Page", title: "Page", rootInstanceId: "instance-1", - systemDataSourceId: "", meta: {}, }, ], @@ -257,7 +255,6 @@ test("normalize page prop with path and hash into string", () => { name: "Page", title: "Page", rootInstanceId: "instance-1", - systemDataSourceId: "", meta: {}, }, ], diff --git a/packages/sdk/src/expression.test.ts b/packages/sdk/src/expression.test.ts index 862ddcdbd505..083039b26772 100644 --- a/packages/sdk/src/expression.test.ts +++ b/packages/sdk/src/expression.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; import { type Diagnostic, - decodeDataSourceVariable, - encodeDataSourceVariable, + decodeDataVariableId, + encodeDataVariableId, executeExpression, isLiteralExpression, lintExpression, @@ -10,6 +10,7 @@ import { getExpressionIdentifiers, parseObjectExpression, generateObjectExpression, + SYSTEM_VARIABLE_ID, } from "./expression"; describe("lint expression", () => { @@ -398,13 +399,17 @@ describe("object expression transformations", () => { }); test("encode/decode variable names", () => { - expect(encodeDataSourceVariable("my--id")).toEqual( + expect(encodeDataVariableId("my--id")).toEqual( "$ws$dataSource$my__DASH____DASH__id" ); - expect(decodeDataSourceVariable(encodeDataSourceVariable("my--id"))).toEqual( + expect(decodeDataVariableId(encodeDataVariableId("my--id"))).toEqual( "my--id" ); - expect(decodeDataSourceVariable("myVarName")).toEqual(undefined); + expect(decodeDataVariableId("myVarName")).toEqual(undefined); + expect(encodeDataVariableId(SYSTEM_VARIABLE_ID)).toEqual("$ws$system"); + expect( + decodeDataVariableId(encodeDataVariableId(SYSTEM_VARIABLE_ID)) + ).toEqual(SYSTEM_VARIABLE_ID); }); test("execute expression", () => { diff --git a/packages/sdk/src/expression.ts b/packages/sdk/src/expression.ts index 40b596d41c8f..aa719938ed22 100644 --- a/packages/sdk/src/expression.ts +++ b/packages/sdk/src/expression.ts @@ -5,8 +5,18 @@ import { parseExpressionAt, } from "acorn"; import { simple } from "acorn-walk"; -import type { DataSources } from "./schema/data-sources"; +import type { DataSource, DataSources } from "./schema/data-sources"; import type { Scope } from "./scope"; +import { ROOT_INSTANCE_ID } from "./instances-utils"; + +export const SYSTEM_VARIABLE_ID = ":system"; + +export const systemParameter: DataSource = { + id: SYSTEM_VARIABLE_ID, + scopeInstanceId: ROOT_INSTANCE_ID, + type: "parameter", + name: "system", +}; export type Diagnostic = { from: number; @@ -317,20 +327,26 @@ const dataSourceVariablePrefix = "$ws$dataSource$"; // here "-" is encoded with "__DASH__' in variable name // https://github.com/ai/nanoid/blob/047686abad8f15aff05f3a2eeedb7c98b6847392/url-alphabet/index.js -export const encodeDataSourceVariable = (id: string) => { +export const encodeDataVariableId = (id: string) => { + if (id === SYSTEM_VARIABLE_ID) { + return "$ws$system"; + } const encoded = id.replaceAll("-", "__DASH__"); return `${dataSourceVariablePrefix}${encoded}`; }; -export { encodeDataSourceVariable as encodeDataVariableId }; +export { encodeDataVariableId as encodeDataSourceVariable }; -export const decodeDataSourceVariable = (name: string) => { +export const decodeDataVariableId = (name: string) => { + if (name === "$ws$system") { + return SYSTEM_VARIABLE_ID; + } if (name.startsWith(dataSourceVariablePrefix)) { const encoded = name.slice(dataSourceVariablePrefix.length); return encoded.replaceAll("__DASH__", "-"); } return; }; -export { decodeDataSourceVariable as decodeDataVariableId }; +export { decodeDataVariableId as decodeDataSourceVariable }; export const generateExpression = ({ expression, @@ -347,8 +363,11 @@ export const generateExpression = ({ expression, executable: true, replaceVariable: (identifier) => { - const depId = decodeDataSourceVariable(identifier); - const dep = depId ? dataSources.get(depId) : undefined; + const depId = decodeDataVariableId(identifier); + let dep = depId ? dataSources.get(depId) : undefined; + if (depId === SYSTEM_VARIABLE_ID) { + dep = systemParameter; + } if (dep) { usedDataSources?.set(dep.id, dep); return scope.getName(dep.id, dep.name); diff --git a/packages/sdk/src/page-meta-generator.test.ts b/packages/sdk/src/page-meta-generator.test.ts index b6bf641d6014..fa27a1b69cad 100644 --- a/packages/sdk/src/page-meta-generator.test.ts +++ b/packages/sdk/src/page-meta-generator.test.ts @@ -15,7 +15,6 @@ test("generate minimal static page meta factory", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `"Page title"`, meta: {}, }, @@ -56,7 +55,6 @@ test("generate complete static page meta factory", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `"Page title"`, meta: { description: `"Page description"`, @@ -131,7 +129,6 @@ test("generate asset url instead of id", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `"Page title"`, meta: { socialImageUrl: `"https://my-image"`, @@ -174,7 +171,6 @@ test("generate custom meta ignoring empty property name", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `"Page title"`, meta: { custom: [ @@ -224,7 +220,6 @@ test("generate page meta factory with variables", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `$ws$dataSource$variableId`, meta: {}, }, @@ -265,7 +260,7 @@ test("generate page meta factory with variables", () => { `); }); -test("generate page meta factory with system", () => { +test("generate page meta factory with page system variable", () => { expect( generatePageMeta({ globalScope: createScope(), @@ -314,6 +309,47 @@ test("generate page meta factory with system", () => { `); }); +test("generate page meta factory with global system variable", () => { + expect( + generatePageMeta({ + globalScope: createScope(), + page: { + id: "", + name: "", + path: "", + rootInstanceId: "", + title: `$ws$system.params.slug`, + meta: {}, + }, + dataSources: new Map(), + assets: new Map(), + }) + ).toMatchInlineSnapshot(` +"export const getPageMeta = ({ + system, + resources, +}: { + system: System; + resources: Record; +}): PageMeta => { + let system_1 = system + return { + title: system_1?.params?.slug, + description: undefined, + excludePageFromSearch: undefined, + language: undefined, + socialImageAssetName: undefined, + socialImageUrl: undefined, + status: undefined, + redirect: undefined, + custom: [ + ], + }; +}; +" +`); +}); + test("generate page meta factory with resources", () => { expect( generatePageMeta({ @@ -323,7 +359,6 @@ test("generate page meta factory with resources", () => { name: "", path: "", rootInstanceId: "", - systemDataSourceId: "", title: `$ws$dataSource$resourceVariableId.data.title`, meta: {}, }, diff --git a/packages/sdk/src/page-meta-generator.ts b/packages/sdk/src/page-meta-generator.ts index b7e3c44e9105..8f3f81dc4578 100644 --- a/packages/sdk/src/page-meta-generator.ts +++ b/packages/sdk/src/page-meta-generator.ts @@ -2,7 +2,7 @@ import type { Asset, Assets } from "./schema/assets"; import type { DataSources } from "./schema/data-sources"; import type { Page } from "./schema/pages"; import { type Scope, createScope } from "./scope"; -import { generateExpression } from "./expression"; +import { generateExpression, SYSTEM_VARIABLE_ID } from "./expression"; export type PageMeta = { title: string; @@ -112,7 +112,10 @@ export const generatePageMeta = ({ continue; } if (dataSource.type === "parameter") { - if (dataSource.id === page.systemDataSourceId) { + if ( + dataSource.id === page.systemDataSourceId || + dataSource.id === SYSTEM_VARIABLE_ID + ) { const valueName = localScope.getName(dataSource.id, dataSource.name); generated += ` let ${valueName} = system\n`; } diff --git a/packages/sdk/src/page-utils.test.ts b/packages/sdk/src/page-utils.test.ts index 244660bd5525..1ea2f387844e 100644 --- a/packages/sdk/src/page-utils.test.ts +++ b/packages/sdk/src/page-utils.test.ts @@ -14,7 +14,6 @@ const pages = { name: "Home", title: "Home", rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemDataSourceId", meta: {}, }, pages: [ @@ -24,7 +23,6 @@ const pages = { name: "Page", title: "Page", rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemDataSourceId", meta: {}, }, ], diff --git a/packages/sdk/src/resources-generator.test.tsx b/packages/sdk/src/resources-generator.test.tsx index 7b3a18654a69..4f3dca51d938 100644 --- a/packages/sdk/src/resources-generator.test.tsx +++ b/packages/sdk/src/resources-generator.test.tsx @@ -1,5 +1,10 @@ import { expect, test } from "vitest"; -import { renderData, $ } from "@webstudio-is/template"; +import { + renderData, + $, + expression, + ResourceValue, +} from "@webstudio-is/template"; import type { Page } from "./schema/pages"; import { createScope } from "./scope"; import { @@ -66,7 +71,6 @@ test("generate variable and use in resources loader", () => { scope: createScope(), page: { rootInstanceId: "body", - systemDataSourceId: "variableSystemId", } as Page, dataSources: toMap([ { @@ -126,7 +130,7 @@ test("generate variable and use in resources loader", () => { `); }); -test("generate system variable and use in resources loader", () => { +test("generate page system variable and use in resources loader", () => { expect( generateResources({ scope: createScope(), @@ -186,6 +190,48 @@ test("generate system variable and use in resources loader", () => { `); }); +test("generate global system variable and use in resources loader", () => { + const myResource = new ResourceValue("My Resource", { + url: expression`"https://my-json.com/" + $ws$system.params.slug`, + method: "post", + headers: [{ name: "Content-Type", value: expression`"application/json"` }], + body: expression`{ body: true }`, + }); + expect( + generateResources({ + scope: createScope(), + page: { + rootInstanceId: "bodyId", + } as Page, + ...renderData( + <$.Body ws:id="bodyId" vars={expression`${myResource}`}> + ), + }) + ).toMatchInlineSnapshot(` + "import type { System, ResourceRequest } from "@webstudio-is/sdk"; + export const getResources = (_props: { system: System }) => { + const system = _props.system + const MyResource: ResourceRequest = { + id: "resource:0", + name: "My Resource", + url: "https://my-json.com/" + system?.params?.slug, + method: "post", + headers: [ + { name: "Content-Type", value: "application/json" }, + ], + body: { body: true }, + } + const _data = new Map([ + ["MyResource", MyResource], + ]) + const _action = new Map([ + ]) + return { data: _data, action: _action } + } + " + `); +}); + test("generate empty resources loader", () => { expect( generateResources({ diff --git a/packages/sdk/src/resources-generator.ts b/packages/sdk/src/resources-generator.ts index 35d49290124c..7799e36ef0e3 100644 --- a/packages/sdk/src/resources-generator.ts +++ b/packages/sdk/src/resources-generator.ts @@ -4,7 +4,7 @@ import type { Resources } from "./schema/resources"; import type { Props } from "./schema/props"; import type { Instance, Instances } from "./schema/instances"; import type { Scope } from "./scope"; -import { generateExpression } from "./expression"; +import { generateExpression, SYSTEM_VARIABLE_ID } from "./expression"; export const generateResources = ({ scope, @@ -72,11 +72,13 @@ export const generateResources = ({ if (dataSource.type === "parameter") { // support only page system parameter - if (dataSource.id !== page.systemDataSourceId) { - continue; + if ( + dataSource.id === page.systemDataSourceId || + dataSource.id === SYSTEM_VARIABLE_ID + ) { + const name = scope.getName(dataSource.id, dataSource.name); + generatedVariables += ` const ${name} = _props.system\n`; } - const name = scope.getName(dataSource.id, dataSource.name); - generatedVariables += ` const ${name} = _props.system\n`; } } diff --git a/packages/sdk/src/schema/pages.ts b/packages/sdk/src/schema/pages.ts index c221d5ef3d2f..e1688677b068 100644 --- a/packages/sdk/src/schema/pages.ts +++ b/packages/sdk/src/schema/pages.ts @@ -50,7 +50,7 @@ const commonPageFields = { title: PageTitle, history: z.optional(z.array(z.string())), rootInstanceId: z.string(), - systemDataSourceId: z.string(), + systemDataSourceId: z.string().optional(), meta: z.object({ description: z.string().optional(), title: z.string().optional(),