diff --git a/fixtures/webstudio-cloudflare-template/.npmrc b/fixtures/webstudio-cloudflare-template/.npmrc new file mode 100644 index 000000000000..5e3cde50ab9f --- /dev/null +++ b/fixtures/webstudio-cloudflare-template/.npmrc @@ -0,0 +1 @@ +force=true diff --git a/fixtures/webstudio-cloudflare-template/app/__generated__/[sitemap.xml].ts b/fixtures/webstudio-cloudflare-template/app/__generated__/$resources.sitemap.xml.ts similarity index 100% rename from fixtures/webstudio-cloudflare-template/app/__generated__/[sitemap.xml].ts rename to fixtures/webstudio-cloudflare-template/app/__generated__/$resources.sitemap.xml.ts diff --git a/fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml].tsx b/fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml]._index.tsx similarity index 91% rename from fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml].tsx rename to fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml]._index.tsx index a9a3faa696b2..2a57cb37211a 100644 --- a/fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml].tsx +++ b/fixtures/webstudio-cloudflare-template/app/routes/[sitemap.xml]._index.tsx @@ -1,5 +1,5 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; export const loader = (arg: LoaderFunctionArgs) => { const host = diff --git a/fixtures/webstudio-custom-template/.npmrc b/fixtures/webstudio-custom-template/.npmrc new file mode 100644 index 000000000000..5e3cde50ab9f --- /dev/null +++ b/fixtures/webstudio-custom-template/.npmrc @@ -0,0 +1 @@ +force=true diff --git a/fixtures/webstudio-custom-template/.webstudio/data.json b/fixtures/webstudio-custom-template/.webstudio/data.json index 72e2f583793b..6530f5573100 100644 --- a/fixtures/webstudio-custom-template/.webstudio/data.json +++ b/fixtures/webstudio-custom-template/.webstudio/data.json @@ -1,10 +1,10 @@ { "build": { - "id": "a7480f00-e7b3-467d-a3f0-45d8b339fe11", + "id": "d48c7c5e-fdd3-4ef6-9173-ff2eaaf851d9", "projectId": "0d856812-61d8-4014-a20a-82e01c0eb8ee", - "version": 228, - "createdAt": "2024-04-23T17:26:49.325Z", - "updatedAt": "2024-04-23T17:26:49.325Z", + "version": 244, + "createdAt": "2024-05-06T21:23:44.086Z", + "updatedAt": "2024-05-06T21:23:44.086Z", "pages": { "meta": { "siteName": "Fixture Site", @@ -69,7 +69,7 @@ }, { "id": "2HLHiU2IwQkvfO0Uuodgo", - "name": "sitemap-html", + "name": "sitemap.xml", "title": "\"Untitled\"", "meta": { "description": "\"\"", @@ -78,11 +78,12 @@ "socialImageUrl": "\"\"", "status": "200", "redirect": "\"\"", + "documentType": "xml", "custom": [] }, "rootInstanceId": "rve0BYRbzAkSCr3Lq-wzi", "systemDataSourceId": "6AKzkKHNkIHWUGJDCoXNz", - "path": "/sitemap-html" + "path": "/sitemap.xml" } ], "folders": [ @@ -1227,80 +1228,6 @@ "alpha": 1 } } - ], - [ - "y72KMNELJQv7WCC2EVikH:mIDIDBHsdlix3RVZAVmv7:marginLeft:", - { - "styleSourceId": "y72KMNELJQv7WCC2EVikH", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "marginLeft", - "value": { - "type": "unit", - "unit": "px", - "value": 20 - } - } - ], - [ - "y72KMNELJQv7WCC2EVikH:mIDIDBHsdlix3RVZAVmv7:display:", - { - "styleSourceId": "y72KMNELJQv7WCC2EVikH", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "display", - "value": { - "type": "keyword", - "value": "flex" - } - } - ], - [ - "crYPhCE3T9eEs-5p-HERt:mIDIDBHsdlix3RVZAVmv7:marginLeft:", - { - "styleSourceId": "crYPhCE3T9eEs-5p-HERt", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "marginLeft", - "value": { - "type": "unit", - "unit": "px", - "value": 20 - } - } - ], - [ - "crYPhCE3T9eEs-5p-HERt:mIDIDBHsdlix3RVZAVmv7:display:", - { - "styleSourceId": "crYPhCE3T9eEs-5p-HERt", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "display", - "value": { - "type": "keyword", - "value": "flex" - } - } - ], - [ - "CPXm9ByL_tFAhAz2Yjh_g:mIDIDBHsdlix3RVZAVmv7:fontWeight:", - { - "styleSourceId": "CPXm9ByL_tFAhAz2Yjh_g", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "fontWeight", - "value": { - "type": "keyword", - "value": "600" - } - } - ], - [ - "5eoCM_8Q2H8ijgCjCDE08:mIDIDBHsdlix3RVZAVmv7:fontWeight:", - { - "styleSourceId": "5eoCM_8Q2H8ijgCjCDE08", - "breakpointId": "mIDIDBHsdlix3RVZAVmv7", - "property": "fontWeight", - "value": { - "type": "keyword", - "value": "600" - } - } ] ], "styleSources": [ @@ -1404,34 +1331,6 @@ "id": "iBzrDKfBww47SfmKZnSMO", "name": "color:lastmod" } - ], - [ - "y72KMNELJQv7WCC2EVikH", - { - "type": "local", - "id": "y72KMNELJQv7WCC2EVikH" - } - ], - [ - "CPXm9ByL_tFAhAz2Yjh_g", - { - "type": "local", - "id": "CPXm9ByL_tFAhAz2Yjh_g" - } - ], - [ - "crYPhCE3T9eEs-5p-HERt", - { - "type": "local", - "id": "crYPhCE3T9eEs-5p-HERt" - } - ], - [ - "5eoCM_8Q2H8ijgCjCDE08", - { - "type": "local", - "id": "5eoCM_8Q2H8ijgCjCDE08" - } ] ], "styleSourceSelections": [ @@ -1511,76 +1410,6 @@ "instanceId": "COafOppxs73Ne4O0Geik0", "values": ["jGTUoz0ux0EK-bikmqnw7"] } - ], - [ - "FD1zWHCq5TVsTSBKWbtV3", - { - "instanceId": "FD1zWHCq5TVsTSBKWbtV3", - "values": ["3JNfFwmMATeV1bH2RfBgF"] - } - ], - [ - "eSgI63Vtow0HYHYWUXn3U", - { - "instanceId": "eSgI63Vtow0HYHYWUXn3U", - "values": ["y72KMNELJQv7WCC2EVikH"] - } - ], - [ - "YT4punX-Bdhl0Dw8l6kum", - { - "instanceId": "YT4punX-Bdhl0Dw8l6kum", - "values": ["nS9BC-b89uRhQ6jI-6el4"] - } - ], - [ - "85KbQ_KRH5fQsA0pDR9G9", - { - "instanceId": "85KbQ_KRH5fQsA0pDR9G9", - "values": ["CPXm9ByL_tFAhAz2Yjh_g"] - } - ], - [ - "qdI0BY3e4j8USqw370Qrs", - { - "instanceId": "qdI0BY3e4j8USqw370Qrs", - "values": ["nS9BC-b89uRhQ6jI-6el4"] - } - ], - [ - "rRH1A7WUjY-_pfauYCmKU", - { - "instanceId": "rRH1A7WUjY-_pfauYCmKU", - "values": ["crYPhCE3T9eEs-5p-HERt"] - } - ], - [ - "6NZJcVjrr4WHS6eqNG1SF", - { - "instanceId": "6NZJcVjrr4WHS6eqNG1SF", - "values": ["iBzrDKfBww47SfmKZnSMO"] - } - ], - [ - "i0u7W1XIPYHdwcD4bmm4Z", - { - "instanceId": "i0u7W1XIPYHdwcD4bmm4Z", - "values": ["5eoCM_8Q2H8ijgCjCDE08"] - } - ], - [ - "NR5D_PEUVi_P7Agy2g1Zr", - { - "instanceId": "NR5D_PEUVi_P7Agy2g1Zr", - "values": ["iBzrDKfBww47SfmKZnSMO"] - } - ], - [ - "Q1mx0RlJAIT37qXjHlt95", - { - "instanceId": "Q1mx0RlJAIT37qXjHlt95", - "values": ["3JNfFwmMATeV1bH2RfBgF"] - } ] ], "props": [ @@ -1805,23 +1634,73 @@ } ], [ - "fBGXDGATUkuv7EEF6Q7Oq", + "g0jOVrFDusEjaGcraMHBo", { - "id": "fBGXDGATUkuv7EEF6Q7Oq", - "instanceId": "fN6n7cnYEEOejAQTZHd46", + "id": "g0jOVrFDusEjaGcraMHBo", + "instanceId": "PjSYDNWuQ4oIg52BShwxB", "name": "data", "type": "expression", "value": "$ws$dataSource$kysfQMiYfpyLK8Z0BwHTn.data" } ], [ - "brJHBwmzzpg0rp5ZyU_VP", + "PUdpnvEE2lyVs7OfBwc7T", { - "id": "brJHBwmzzpg0rp5ZyU_VP", - "instanceId": "fN6n7cnYEEOejAQTZHd46", + "id": "PUdpnvEE2lyVs7OfBwc7T", + "instanceId": "PjSYDNWuQ4oIg52BShwxB", "name": "item", "type": "parameter", - "value": "Jli9j5EFM9nbJJKyjmG5N" + "value": "C3teXw_3uxq4VBGE_J2yo" + } + ], + [ + "9JRtdabNdYoyqICQaqsF4", + { + "id": "9JRtdabNdYoyqICQaqsF4", + "instanceId": "SKzEKWw1VtVVFvUcIWuUp", + "name": "tag", + "type": "string", + "value": "url" + } + ], + [ + "fVOxC1Eyp2-f4iAh_-u_O", + { + "id": "fVOxC1Eyp2-f4iAh_-u_O", + "instanceId": "9NJGnzZG3iPZs78XPTHhH", + "name": "tag", + "type": "string", + "value": "loc" + } + ], + [ + "L3gLkXqCMivcNj9pLrKZs", + { + "id": "L3gLkXqCMivcNj9pLrKZs", + "instanceId": "IjNaHLHI4gWStV8GvhijX", + "name": "tag", + "type": "string", + "value": "lastmod" + } + ], + [ + "sDNoSuQSDz9hSGBTNmkay", + { + "id": "sDNoSuQSDz9hSGBTNmkay", + "instanceId": "cgaMXxOMMAh4H-u-MB3_0", + "name": "tag", + "type": "string", + "value": "urlset" + } + ], + [ + "PLqbTfaDLOS5hxJ_dERqI", + { + "id": "PLqbTfaDLOS5hxJ_dERqI", + "instanceId": "cgaMXxOMMAh4H-u-MB3_0", + "name": "xmlns", + "type": "string", + "value": "http://www.sitemaps.org/schemas/sitemap/0.9" } ] ], @@ -1873,26 +1752,13 @@ } ], [ - "Jli9j5EFM9nbJJKyjmG5N", + "C3teXw_3uxq4VBGE_J2yo", { "type": "parameter", - "id": "Jli9j5EFM9nbJJKyjmG5N", - "scopeInstanceId": "fN6n7cnYEEOejAQTZHd46", + "id": "C3teXw_3uxq4VBGE_J2yo", + "scopeInstanceId": "PjSYDNWuQ4oIg52BShwxB", "name": "url" } - ], - [ - "-tLBLywqXy2NSb7eonEuq", - { - "type": "variable", - "id": "-tLBLywqXy2NSb7eonEuq", - "scopeInstanceId": "rve0BYRbzAkSCr3Lq-wzi", - "name": "origin", - "value": { - "type": "string", - "value": "https://my-site.cc" - } - } ] ], "resources": [ @@ -2250,215 +2116,82 @@ "children": [ { "type": "id", - "value": "fN6n7cnYEEOejAQTZHd46" + "value": "cgaMXxOMMAh4H-u-MB3_0" } ] } ], [ - "fN6n7cnYEEOejAQTZHd46", + "cgaMXxOMMAh4H-u-MB3_0", { "type": "instance", - "id": "fN6n7cnYEEOejAQTZHd46", - "component": "ws:collection", - "label": "SitemapXml", + "id": "cgaMXxOMMAh4H-u-MB3_0", + "component": "XmlNode", "children": [ { "type": "id", - "value": "qNCcAfJQFIBEGM99hOGcj" + "value": "PjSYDNWuQ4oIg52BShwxB" } ] } ], [ - "qNCcAfJQFIBEGM99hOGcj", + "PjSYDNWuQ4oIg52BShwxB", { "type": "instance", - "id": "qNCcAfJQFIBEGM99hOGcj", - "component": "Box", - "label": "url", + "id": "PjSYDNWuQ4oIg52BShwxB", + "component": "ws:collection", + "label": "urls", "children": [ { "type": "id", - "value": "FD1zWHCq5TVsTSBKWbtV3" - }, - { - "type": "id", - "value": "eSgI63Vtow0HYHYWUXn3U" - }, - { - "type": "id", - "value": "rRH1A7WUjY-_pfauYCmKU" - }, - { - "type": "id", - "value": "Q1mx0RlJAIT37qXjHlt95" - } - ] - } - ], - [ - "FD1zWHCq5TVsTSBKWbtV3", - { - "type": "instance", - "id": "FD1zWHCq5TVsTSBKWbtV3", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" + "value": "SKzEKWw1VtVVFvUcIWuUp" } ] } ], [ - "eSgI63Vtow0HYHYWUXn3U", + "SKzEKWw1VtVVFvUcIWuUp", { "type": "instance", - "id": "eSgI63Vtow0HYHYWUXn3U", - "component": "Box", - "label": "loc", + "id": "SKzEKWw1VtVVFvUcIWuUp", + "component": "XmlNode", "children": [ { "type": "id", - "value": "YT4punX-Bdhl0Dw8l6kum" - }, - { - "type": "id", - "value": "85KbQ_KRH5fQsA0pDR9G9" + "value": "9NJGnzZG3iPZs78XPTHhH" }, { "type": "id", - "value": "qdI0BY3e4j8USqw370Qrs" + "value": "IjNaHLHI4gWStV8GvhijX" } ] } ], [ - "YT4punX-Bdhl0Dw8l6kum", + "9NJGnzZG3iPZs78XPTHhH", { "type": "instance", - "id": "YT4punX-Bdhl0Dw8l6kum", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" - } - ] - } - ], - [ - "85KbQ_KRH5fQsA0pDR9G9", - { - "type": "instance", - "id": "85KbQ_KRH5fQsA0pDR9G9", - "component": "Text", - "label": "value", + "id": "9NJGnzZG3iPZs78XPTHhH", + "component": "XmlNode", "children": [ { "type": "expression", - "value": "$ws$dataSource$__DASH__tLBLywqXy2NSb7eonEuq + $ws$dataSource$Jli9j5EFM9nbJJKyjmG5N.path" - } - ] - } - ], - [ - "qdI0BY3e4j8USqw370Qrs", - { - "type": "instance", - "id": "qdI0BY3e4j8USqw370Qrs", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" - } - ] - } - ], - [ - "rRH1A7WUjY-_pfauYCmKU", - { - "type": "instance", - "id": "rRH1A7WUjY-_pfauYCmKU", - "component": "Box", - "label": "lastmod", - "children": [ - { - "type": "id", - "value": "6NZJcVjrr4WHS6eqNG1SF" - }, - { - "type": "id", - "value": "i0u7W1XIPYHdwcD4bmm4Z" - }, - { - "type": "id", - "value": "NR5D_PEUVi_P7Agy2g1Zr" + "value": "`${$ws$dataSource$6AKzkKHNkIHWUGJDCoXNz.origin ?? '${ORIGIN}'}${$ws$dataSource$C3teXw_3uxq4VBGE_J2yo.path}`" } ] } ], [ - "6NZJcVjrr4WHS6eqNG1SF", + "IjNaHLHI4gWStV8GvhijX", { "type": "instance", - "id": "6NZJcVjrr4WHS6eqNG1SF", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" - } - ] - } - ], - [ - "i0u7W1XIPYHdwcD4bmm4Z", - { - "type": "instance", - "id": "i0u7W1XIPYHdwcD4bmm4Z", - "component": "Text", - "label": "value", + "id": "IjNaHLHI4gWStV8GvhijX", + "component": "XmlNode", "children": [ { "type": "expression", - "value": "$ws$dataSource$Jli9j5EFM9nbJJKyjmG5N.lastModified" - } - ] - } - ], - [ - "NR5D_PEUVi_P7Agy2g1Zr", - { - "type": "instance", - "id": "NR5D_PEUVi_P7Agy2g1Zr", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" - } - ] - } - ], - [ - "Q1mx0RlJAIT37qXjHlt95", - { - "type": "instance", - "id": "Q1mx0RlJAIT37qXjHlt95", - "component": "Text", - "label": "", - "children": [ - { - "type": "text", - "value": "" + "value": "$ws$dataSource$C3teXw_3uxq4VBGE_J2yo.lastModified" } ] } @@ -2533,7 +2266,7 @@ }, { "id": "2HLHiU2IwQkvfO0Uuodgo", - "name": "sitemap-html", + "name": "sitemap.xml", "title": "\"Untitled\"", "meta": { "description": "\"\"", @@ -2542,11 +2275,12 @@ "socialImageUrl": "\"\"", "status": "200", "redirect": "\"\"", + "documentType": "xml", "custom": [] }, "rootInstanceId": "rve0BYRbzAkSCr3Lq-wzi", "systemDataSourceId": "6AKzkKHNkIHWUGJDCoXNz", - "path": "/sitemap-html" + "path": "/sitemap.xml" } ], "assets": [ diff --git a/fixtures/webstudio-custom-template/app/__generated__/$resources.sitemap.xml.ts b/fixtures/webstudio-custom-template/app/__generated__/$resources.sitemap.xml.ts new file mode 100644 index 000000000000..dcd9b27f5822 --- /dev/null +++ b/fixtures/webstudio-custom-template/app/__generated__/$resources.sitemap.xml.ts @@ -0,0 +1,18 @@ +export const sitemap = [ + { + path: "/", + lastModified: "2024-05-06", + }, + { + path: "/script-test", + lastModified: "2024-05-06", + }, + { + path: "/world", + lastModified: "2024-05-06", + }, + { + path: "/sitemap.xml", + lastModified: "2024-05-06", + }, +]; diff --git a/fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.tsx b/fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.tsx deleted file mode 100644 index 53b70d2992d3..000000000000 --- a/fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable */ -/* This is a auto generated file for building the project */ - -import { Fragment, useState } from "react"; -import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; -import { useResource } from "@webstudio-is/react-sdk"; -import { Body as Body } from "@webstudio-is/sdk-components-react-remix"; -import { Box as Box, Text as Text } from "@webstudio-is/sdk-components-react"; - -export const siteName = "Fixture Site"; - -export const favIconAsset: ImageAsset | undefined = { - id: "cd1e9fad-8df1-45c6-800f-05fda2d2469f", - name: "home_wsKvRSqvkajPPBeycZ-C8.svg", - description: null, - projectId: "0d856812-61d8-4014-a20a-82e01c0eb8ee", - size: 3350, - type: "image", - format: "svg", - createdAt: "2023-10-30T20:35:47.113Z", - meta: { width: 16, height: 16 }, -}; - -export const socialImageAsset: ImageAsset | undefined = undefined; - -// Font assets on current page (can be preloaded) -export const pageFontAssets: FontAsset[] = []; - -export const pageBackgroundImageAssets: ImageAsset[] = []; - -const Page = ({}: { system: any }) => { - let [origin, set$origin] = useState("https://my-site.cc"); - let sitemapxml = useResource("sitemapxml_1"); - return ( - - {sitemapxml?.data?.map((url: any, index: number) => ( - - - - {""} - - - - {""} - - - {origin + url?.path} - - - {""} - - - - - {""} - - - {url?.lastModified} - - - {""} - - - - {""} - - - - ))} - - ); -}; - -export { Page }; diff --git a/fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.server.tsx b/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.server.tsx similarity index 97% rename from fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.server.tsx rename to fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.server.tsx index 065bf11aa79a..3b21ec5193a9 100644 --- a/fixtures/webstudio-custom-template/app/__generated__/[sitemap-html]._index.server.tsx +++ b/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.server.tsx @@ -3,7 +3,7 @@ import type { PageMeta } from "@webstudio-is/sdk"; import { loadResource, isLocalResource, type System } from "@webstudio-is/sdk"; -import { sitemap } from "./[sitemap.xml]"; +import { sitemap } from "./$resources.sitemap.xml"; export const loadResources = async (_props: { system: System }) => { const customFetch: typeof fetch = (input, init) => { if (typeof input !== "string") { diff --git a/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.tsx b/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.tsx new file mode 100644 index 000000000000..07508509ca42 --- /dev/null +++ b/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml]._index.tsx @@ -0,0 +1,70 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import { Fragment, useState } from "react"; +import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; +import { useResource } from "@webstudio-is/react-sdk"; +import { Body as Body } from "@webstudio-is/sdk-components-react-remix"; +import { XmlNode as XmlNode } from "@webstudio-is/sdk-components-react"; + +export const siteName = "Fixture Site"; + +export const favIconAsset: ImageAsset | undefined = { + id: "cd1e9fad-8df1-45c6-800f-05fda2d2469f", + name: "home_wsKvRSqvkajPPBeycZ-C8.svg", + description: null, + projectId: "0d856812-61d8-4014-a20a-82e01c0eb8ee", + size: 3350, + type: "image", + format: "svg", + createdAt: "2023-10-30T20:35:47.113Z", + meta: { width: 16, height: 16 }, +}; + +export const socialImageAsset: ImageAsset | undefined = undefined; + +// Font assets on current page (can be preloaded) +export const pageFontAssets: FontAsset[] = []; + +export const pageBackgroundImageAssets: ImageAsset[] = []; + +const Page = ({ system: system }: { system: any }) => { + let sitemapxml = useResource("sitemapxml_1"); + return ( + + + {sitemapxml?.data?.map((url: any, index: number) => ( + + + + {`${system?.origin ?? "${ORIGIN}"}${url?.path}`} + + + {url?.lastModified} + + + + ))} + + + ); +}; + +export { Page }; diff --git a/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml].ts b/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml].ts deleted file mode 100644 index f01802de475d..000000000000 --- a/fixtures/webstudio-custom-template/app/__generated__/[sitemap.xml].ts +++ /dev/null @@ -1,18 +0,0 @@ -export const sitemap = [ - { - path: "/", - lastModified: "2024-04-23", - }, - { - path: "/script-test", - lastModified: "2024-04-23", - }, - { - path: "/world", - lastModified: "2024-04-23", - }, - { - path: "/sitemap-html", - lastModified: "2024-04-23", - }, -]; diff --git a/fixtures/webstudio-custom-template/app/__generated__/index.css b/fixtures/webstudio-custom-template/app/__generated__/index.css index c284bf04b2cc..a90916278116 100644 --- a/fixtures/webstudio-custom-template/app/__generated__/index.css +++ b/fixtures/webstudio-custom-template/app/__generated__/index.css @@ -223,15 +223,6 @@ html { display: block; height: auto; } - div:where([data-ws-component="Text"]) { - box-sizing: border-box; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - outline-width: 1px; - min-height: 1em; - } } @media all { .c12poxag { @@ -419,19 +410,4 @@ html { .cub4hpc { flex-basis: 0px; } - .cwnydv4 { - color: rgba(36, 12, 233, 1); - } - .cnvcd8b { - margin-left: 20px; - } - .c1s70es7 { - color: rgba(8, 137, 17, 1); - } - .c5fu8k4 { - font-weight: 600; - } - .cgkkof0 { - color: rgba(208, 24, 79, 1); - } } diff --git a/fixtures/webstudio-custom-template/app/routes/[sitemap-html]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[sitemap-html]._index.tsx deleted file mode 100644 index 1bc27c25b710..000000000000 --- a/fixtures/webstudio-custom-template/app/routes/[sitemap-html]._index.tsx +++ /dev/null @@ -1,321 +0,0 @@ -/* eslint-disable camelcase */ -import { - type ServerRuntimeMetaFunction as MetaFunction, - type LinksFunction, - type LinkDescriptor, - type ActionFunctionArgs, - type LoaderFunctionArgs, - type HeadersFunction, - json, - redirect, -} from "@remix-run/server-runtime"; -import { useLoaderData } from "@remix-run/react"; -import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; -import { - Page, - siteName, - favIconAsset, - socialImageAsset, - pageFontAssets, - pageBackgroundImageAssets, -} from "../__generated__/[sitemap-html]._index"; -import { - formsProperties, - loadResources, - getPageMeta, - getRemixParams, - projectId, - contactEmail, -} from "../__generated__/[sitemap-html]._index.server"; - -import css from "../__generated__/index.css?url"; -import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs"; - -export const loader = async (arg: LoaderFunctionArgs) => { - const url = new URL(arg.request.url); - const host = - arg.request.headers.get("x-forwarded-host") || - arg.request.headers.get("host") || - ""; - url.host = host; - url.protocol = "https"; - - const params = getRemixParams(arg.params); - const system = { - params, - search: Object.fromEntries(url.searchParams), - origin: url.origin, - }; - - const resources = await loadResources({ system }); - const pageMeta = getPageMeta({ system, resources }); - - if (pageMeta.redirect) { - const status = - pageMeta.status === 301 || pageMeta.status === 302 - ? pageMeta.status - : 302; - return redirect(pageMeta.redirect, status); - } - - // typecheck - arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; - - return json( - { - host, - url: url.href, - excludeFromSearch: arg.context.EXCLUDE_FROM_SEARCH, - system, - resources, - pageMeta, - }, - // No way for current information to change, so add cache for 10 minutes - // In case of CRM Data, this should be set to 0 - { - status: pageMeta.status, - headers: { - "Cache-Control": "public, max-age=600", - "x-ws-language": pageMeta.language ?? "en", - }, - } - ); -}; - -export const headers: HeadersFunction = ({ loaderHeaders }) => { - return { - "Cache-Control": "public, max-age=0, must-revalidate", - "x-ws-language": loaderHeaders.get("x-ws-language") ?? "", - }; -}; - -export const meta: MetaFunction = ({ data }) => { - const metas: ReturnType = []; - if (data === undefined) { - return metas; - } - const { pageMeta } = data; - - if (data.url) { - metas.push({ - property: "og:url", - content: data.url, - }); - } - - if (pageMeta.title) { - metas.push({ title: pageMeta.title }); - - metas.push({ - property: "og:title", - content: pageMeta.title, - }); - } - - metas.push({ property: "og:type", content: "website" }); - - const origin = `https://${data.host}`; - - if (siteName) { - metas.push({ - property: "og:site_name", - content: siteName, - }); - metas.push({ - "script:ld+json": { - "@context": "https://schema.org", - "@type": "WebSite", - name: siteName, - url: origin, - }, - }); - } - - if (pageMeta.excludePageFromSearch || data.excludeFromSearch) { - metas.push({ - name: "robots", - content: "noindex, nofollow", - }); - } - - if (pageMeta.description) { - metas.push({ - name: "description", - content: pageMeta.description, - }); - metas.push({ - property: "og:description", - content: pageMeta.description, - }); - } - - if (socialImageAsset) { - metas.push({ - property: "og:image", - content: `https://${data.host}${imageLoader({ - src: socialImageAsset.name, - // Do not transform social image (not enough information do we need to do this) - format: "raw", - })}`, - }); - } else if (pageMeta.socialImageUrl) { - metas.push({ - property: "og:image", - content: pageMeta.socialImageUrl, - }); - } - - metas.push(...pageMeta.custom); - - return metas; -}; - -export const links: LinksFunction = () => { - const result: LinkDescriptor[] = []; - - result.push({ - rel: "stylesheet", - href: css, - }); - - if (favIconAsset) { - result.push({ - rel: "icon", - href: imageLoader({ - src: favIconAsset.name, - width: 128, - quality: 100, - format: "auto", - }), - type: undefined, - }); - } else { - result.push({ - rel: "icon", - href: "/favicon.ico", - type: "image/x-icon", - }); - - result.push({ - rel: "shortcut icon", - href: "/favicon.ico", - type: "image/x-icon", - }); - } - - for (const asset of pageFontAssets) { - result.push({ - rel: "preload", - href: `${assetBaseUrl}${asset.name}`, - as: "font", - crossOrigin: "anonymous", - }); - } - - for (const backgroundImageAsset of pageBackgroundImageAssets) { - result.push({ - rel: "preload", - href: `${assetBaseUrl}${backgroundImageAsset.name}`, - as: "image", - }); - } - - return result; -}; - -const getRequestHost = (request: Request): string => - request.headers.get("x-forwarded-host") || request.headers.get("host") || ""; - -const getMethod = (value: string | undefined) => { - if (value === undefined) { - return "post"; - } - - switch (value.toLowerCase()) { - case "get": - return "get"; - default: - return "post"; - } -}; - -export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); - - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } - - const formProperties = formsProperties.get(formId); - - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; - - if (contactEmail === undefined) { - return { success: false }; - } - - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); - pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); - } - } - - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); - - return result; -}; - -const Outlet = () => { - const { system, resources } = useLoaderData(); - return ( - - - - ); -}; - -export default Outlet; diff --git a/fixtures/webstudio-custom-template/app/routes/[sitemap.xml]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[sitemap.xml]._index.tsx new file mode 100644 index 000000000000..a549ca2b1987 --- /dev/null +++ b/fixtures/webstudio-custom-template/app/routes/[sitemap.xml]._index.tsx @@ -0,0 +1,71 @@ +/* eslint-disable camelcase */ +import { + type ServerRuntimeMetaFunction as MetaFunction, + type LoaderFunctionArgs, + type HeadersFunction, + redirect, +} from "@remix-run/server-runtime"; +import { ReactSdkContext } from "@webstudio-is/react-sdk"; +import { Page } from "../__generated__/[sitemap.xml]._index"; +import { + loadResources, + getPageMeta, + getRemixParams, +} from "../__generated__/[sitemap.xml]._index.server"; + +import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs"; +import { renderToString } from "react-dom/server"; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources({ system }); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + return redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + const text = renderToString( + + + + ); + + return new Response(`\n${text}`); +}; + +export const headers: HeadersFunction = ({ loaderHeaders }) => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + "Content-Type": "application/xml", + }; +}; diff --git a/fixtures/webstudio-remix-netlify-edge-functions/.npmrc b/fixtures/webstudio-remix-netlify-edge-functions/.npmrc new file mode 100644 index 000000000000..5e3cde50ab9f --- /dev/null +++ b/fixtures/webstudio-remix-netlify-edge-functions/.npmrc @@ -0,0 +1 @@ +force=true diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/__generated__/[sitemap.xml].ts b/fixtures/webstudio-remix-netlify-edge-functions/app/__generated__/$resources.sitemap.xml.ts similarity index 100% rename from fixtures/webstudio-remix-netlify-edge-functions/app/__generated__/[sitemap.xml].ts rename to fixtures/webstudio-remix-netlify-edge-functions/app/__generated__/$resources.sitemap.xml.ts diff --git a/fixtures/webstudio-custom-template/app/routes/[sitemap.xml].tsx b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml]._index.tsx similarity index 91% rename from fixtures/webstudio-custom-template/app/routes/[sitemap.xml].tsx rename to fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml]._index.tsx index a9a3faa696b2..2a57cb37211a 100644 --- a/fixtures/webstudio-custom-template/app/routes/[sitemap.xml].tsx +++ b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml]._index.tsx @@ -1,5 +1,5 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; export const loader = (arg: LoaderFunctionArgs) => { const host = diff --git a/fixtures/webstudio-remix-netlify-functions/.npmrc b/fixtures/webstudio-remix-netlify-functions/.npmrc new file mode 100644 index 000000000000..5e3cde50ab9f --- /dev/null +++ b/fixtures/webstudio-remix-netlify-functions/.npmrc @@ -0,0 +1 @@ +force=true diff --git a/fixtures/webstudio-remix-netlify-functions/app/__generated__/[sitemap.xml].ts b/fixtures/webstudio-remix-netlify-functions/app/__generated__/$resources.sitemap.xml.ts similarity index 100% rename from fixtures/webstudio-remix-netlify-functions/app/__generated__/[sitemap.xml].ts rename to fixtures/webstudio-remix-netlify-functions/app/__generated__/$resources.sitemap.xml.ts diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml].tsx b/fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml]._index.tsx similarity index 91% rename from fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml].tsx rename to fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml]._index.tsx index a9a3faa696b2..2a57cb37211a 100644 --- a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/[sitemap.xml].tsx +++ b/fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml]._index.tsx @@ -1,5 +1,5 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; export const loader = (arg: LoaderFunctionArgs) => { const host = diff --git a/fixtures/webstudio-remix-vercel/.npmrc b/fixtures/webstudio-remix-vercel/.npmrc new file mode 100644 index 000000000000..5e3cde50ab9f --- /dev/null +++ b/fixtures/webstudio-remix-vercel/.npmrc @@ -0,0 +1 @@ +force=true diff --git a/fixtures/webstudio-remix-vercel/app/__generated__/[sitemap.xml].ts b/fixtures/webstudio-remix-vercel/app/__generated__/$resources.sitemap.xml.ts similarity index 100% rename from fixtures/webstudio-remix-vercel/app/__generated__/[sitemap.xml].ts rename to fixtures/webstudio-remix-vercel/app/__generated__/$resources.sitemap.xml.ts diff --git a/fixtures/webstudio-remix-vercel/app/__generated__/[resources]._index.server.tsx b/fixtures/webstudio-remix-vercel/app/__generated__/[resources]._index.server.tsx index 2e54f03c1fa3..81f92eb46c75 100644 --- a/fixtures/webstudio-remix-vercel/app/__generated__/[resources]._index.server.tsx +++ b/fixtures/webstudio-remix-vercel/app/__generated__/[resources]._index.server.tsx @@ -3,7 +3,7 @@ import type { PageMeta } from "@webstudio-is/sdk"; import { loadResource, isLocalResource, type System } from "@webstudio-is/sdk"; -import { sitemap } from "./[sitemap.xml]"; +import { sitemap } from "./$resources.sitemap.xml"; export const loadResources = async (_props: { system: System }) => { const customFetch: typeof fetch = (input, init) => { if (typeof input !== "string") { diff --git a/fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml].tsx b/fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml]._index.tsx similarity index 91% rename from fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml].tsx rename to fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml]._index.tsx index a9a3faa696b2..2a57cb37211a 100644 --- a/fixtures/webstudio-remix-netlify-functions/app/routes/[sitemap.xml].tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml]._index.tsx @@ -1,5 +1,5 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; export const loader = (arg: LoaderFunctionArgs) => { const host = diff --git a/fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml].tsx b/fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml].tsx deleted file mode 100644 index a9a3faa696b2..000000000000 --- a/fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml].tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; - -export const loader = (arg: LoaderFunctionArgs) => { - const host = - arg.request.headers.get("x-forwarded-host") || - arg.request.headers.get("host") || - ""; - - const urls = sitemap.map((page) => { - const url = new URL(`https://${host}${page.path}`); - - return ` - - ${url.href} - ${page.lastModified.split("T")[0]} - - `; - }); - - return new Response( - ` - -${urls.join("")} - - `, - { - headers: { - "Content-Type": "application/xml", - }, - status: 200, - } - ); -}; diff --git a/packages/cli/templates/defaults/app/__generated__/[sitemap.xml].ts b/packages/cli/__generated__/$resources.sitemap.xml.ts similarity index 100% rename from packages/cli/templates/defaults/app/__generated__/[sitemap.xml].ts rename to packages/cli/__generated__/$resources.sitemap.xml.ts diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 2dd6a24bca19..81c3da86dc8f 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ import { exit, argv } from "node:process"; // eslint-disable-next-line import/no-internal-modules import { hideBin } from "yargs/helpers"; import { GLOBAL_CONFIG_FILE } from "./config"; -import { ensureFileInPath } from "./fs-utils"; +import { createFileIfNotExists } from "./fs-utils"; import { link, linkOptions } from "./commands/link"; import { sync, syncOptions } from "./commands/sync"; import { build, buildOptions } from "./commands/build"; @@ -13,7 +13,7 @@ import type { CommonYargsArgv } from "./commands/yargs-types"; export const main = async () => { try { - await ensureFileInPath(GLOBAL_CONFIG_FILE, "{}"); + await createFileIfNotExists(GLOBAL_CONFIG_FILE, "{}"); const cmd: CommonYargsArgv = makeCLI(hideBin(argv)) .strict() diff --git a/packages/cli/src/commands/init-flow.ts b/packages/cli/src/commands/init-flow.ts index e5c49eaeefce..420c78e71b92 100644 --- a/packages/cli/src/commands/init-flow.ts +++ b/packages/cli/src/commands/init-flow.ts @@ -1,4 +1,4 @@ -import { ensureFolderExists, isFileExists } from "../fs-utils"; +import { createFolderIfNotExists, isFileExists } from "../fs-utils"; import { chdir, cwd } from "node:process"; import { join } from "node:path"; import ora from "ora"; @@ -43,7 +43,7 @@ export const initFlow = async ( if (folderName === undefined) { throw new Error("Folder name is required"); } - await ensureFolderExists(join(cwd(), folderName)); + await createFolderIfNotExists(join(cwd(), folderName)); chdir(join(cwd(), folderName)); } diff --git a/packages/cli/src/commands/link.ts b/packages/cli/src/commands/link.ts index e52f15222223..215f0d576935 100644 --- a/packages/cli/src/commands/link.ts +++ b/packages/cli/src/commands/link.ts @@ -9,7 +9,7 @@ import { jsonToGlobalConfig, type LocalConfig, } from "../config"; -import { ensureFileInPath } from "../fs-utils"; +import { createFileIfNotExists } from "../fs-utils"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, @@ -72,7 +72,7 @@ export const link = async ( projectId, }; - await ensureFileInPath( + await createFileIfNotExists( join(cwd(), LOCAL_CONFIG_FILE), JSON.stringify(localConfig, null, 2) ); diff --git a/packages/cli/src/commands/sync.ts b/packages/cli/src/commands/sync.ts index 1fb073aa1298..9e406443c3b7 100644 --- a/packages/cli/src/commands/sync.ts +++ b/packages/cli/src/commands/sync.ts @@ -9,7 +9,7 @@ import { } from "@webstudio-is/http-client"; import pc from "picocolors"; -import { ensureFileInPath, isFileExists } from "../fs-utils"; +import { createFileIfNotExists, isFileExists } from "../fs-utils"; import { GLOBAL_CONFIG_FILE, LOCAL_CONFIG_FILE, @@ -118,7 +118,7 @@ export const sync = async ( spinner.text = "Saving project data to config file"; const localBuildFilePath = join(cwd(), LOCAL_DATA_FILE); - await ensureFileInPath(localBuildFilePath); + await createFileIfNotExists(localBuildFilePath); await writeFile(localBuildFilePath, JSON.stringify(project, null, 2), "utf8"); spinner.succeed("Project data synced successfully"); diff --git a/packages/cli/src/fs-utils.ts b/packages/cli/src/fs-utils.ts index bcbbdefb41d2..c8dae8658f06 100644 --- a/packages/cli/src/fs-utils.ts +++ b/packages/cli/src/fs-utils.ts @@ -16,10 +16,13 @@ export const isFileExists = async (filePath: string) => { } }; -export const ensureFileInPath = async (filePath: string, content?: string) => { +export const createFileIfNotExists = async ( + filePath: string, + content?: string +) => { const dir = dirname(filePath); - await ensureFolderExists(dir); + await createFolderIfNotExists(dir); try { await access(filePath, constants.F_OK); @@ -28,7 +31,7 @@ export const ensureFileInPath = async (filePath: string, content?: string) => { } }; -export const ensureFolderExists = async (folderPath: string) => { +export const createFolderIfNotExists = async (folderPath: string) => { try { await access(folderPath, constants.F_OK); } catch { diff --git a/packages/cli/src/prebuild.ts b/packages/cli/src/prebuild.ts index 7903a244c424..2e17ac1ab7cb 100644 --- a/packages/cli/src/prebuild.ts +++ b/packages/cli/src/prebuild.ts @@ -55,8 +55,8 @@ import * as remixComponentMetas from "@webstudio-is/sdk-components-react-remix/m import * as radixComponentMetas from "@webstudio-is/sdk-components-react-radix/metas"; import { LOCAL_DATA_FILE } from "./config"; import { - ensureFileInPath, - ensureFolderExists, + createFileIfNotExists, + createFolderIfNotExists, loadJSONFile, isFileExists, } from "./fs-utils"; @@ -97,7 +97,7 @@ export const downloadAsset = async ( try { await access(assetPath); } catch { - await ensureFolderExists(dirname(assetPath)); + await createFolderIfNotExists(dirname(assetPath)); try { const response = await fetch(url); @@ -489,15 +489,30 @@ export const prebuild = async (options: { atomic: siteData.build.pages.compiler?.atomicStyles ?? true, }); - await ensureFileInPath(join(generatedDir, "index.css"), cssText); + await createFileIfNotExists(join(generatedDir, "index.css"), cssText); spinner.text = "Generating routes and pages"; - const routeTemplatePath = normalize(join(cwd(), "app/routes/template.tsx")); + // MARK: - Route templates + const routeTemplatesDir = join(cwd(), "app/route-templates"); - const routeFileTemplate = await readFile(routeTemplatePath, "utf8"); + const routeTemplatePath = normalize( + join(routeTemplatesDir, "html-doc-type.tsx") + ); + const routeXmlTemplatePath = normalize( + join(routeTemplatesDir, "xml-doc-type.tsx") + ); + const implicitSiteMapXmlPath = normalize( + join(routeTemplatesDir, "implicit-sitemap.tsx") + ); - await rm(routeTemplatePath); + const routeFileTemplate = await readFile(routeTemplatePath, "utf8"); + const routeXmlFileTemplate = await readFile(routeXmlTemplatePath, "utf8"); + const implicitSiteMapTemplate = await readFile( + implicitSiteMapXmlPath, + "utf8" + ); + await rm(routeTemplatesDir, { recursive: true, force: true }); for (const [pageId, pageComponents] of Object.entries(componentsByPage)) { const scope = createScope([ @@ -508,10 +523,12 @@ export const prebuild = async (options: { "Page", "_props", ]); + const namespaces = new Map< string, Set<[shortName: string, componentName: string]> >(); + const BASE_NAMESPACE = "@webstudio-is/sdk-components-react"; const REMIX_NAMESPACE = "@webstudio-is/sdk-components-react-remix"; @@ -539,6 +556,7 @@ export const prebuild = async (options: { } let componentImports = ""; + for (const [namespace, componentsSet] of namespaces.entries()) { const specifiers = Array.from(componentsSet) .map( @@ -550,6 +568,7 @@ export const prebuild = async (options: { } const pageData = siteDataByPage[pageId]; + const documentType = pageData.page.meta.documentType ?? "html"; const pageFontAssets = fontAssetsByPage[pageId]; const pageBackgroundImageAssets = backgroundImageAssetsByPage[pageId]; @@ -589,62 +608,66 @@ export const prebuild = async (options: { const pageMeta = pageData.page.meta; const favIconAsset = assets.get(projectMeta?.faviconAssetId ?? ""); const socialImageAsset = assets.get(pageMeta.socialImageAssetId ?? ""); + const pageExports = `/* eslint-disable */ -/* This is a auto generated file for building the project */ \n + /* This is a auto generated file for building the project */ \n -import { Fragment, useState } from "react"; -import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; -import { useResource } from "@webstudio-is/react-sdk"; -${componentImports} + import { Fragment, useState } from "react"; + import type { FontAsset, ImageAsset } from "@webstudio-is/sdk"; + import { useResource } from "@webstudio-is/react-sdk"; + ${componentImports} -export const siteName = ${JSON.stringify(projectMeta?.siteName)}; + export const siteName = ${JSON.stringify(projectMeta?.siteName)}; -export const favIconAsset: ImageAsset | undefined = - ${JSON.stringify(favIconAsset)}; + export const favIconAsset: ImageAsset | undefined = + ${JSON.stringify(favIconAsset)}; -export const socialImageAsset: ImageAsset | undefined = - ${JSON.stringify(socialImageAsset)}; + export const socialImageAsset: ImageAsset | undefined = + ${JSON.stringify(socialImageAsset)}; -// Font assets on current page (can be preloaded) -export const pageFontAssets: FontAsset[] = - ${JSON.stringify(pageFontAssets)} + // Font assets on current page (can be preloaded) + export const pageFontAssets: FontAsset[] = + ${JSON.stringify(pageFontAssets)} -export const pageBackgroundImageAssets: ImageAsset[] = - ${JSON.stringify(pageBackgroundImageAssets)} + export const pageBackgroundImageAssets: ImageAsset[] = + ${JSON.stringify(pageBackgroundImageAssets)} -${pageComponent} + ${pageComponent} + + export { Page } + `; -export { Page } -`; const serverExports = `/* eslint-disable */ -/* This is a auto generated file for building the project */ \n + /* This is a auto generated file for building the project */ \n -import type { PageMeta } from "@webstudio-is/sdk"; -${generateResourcesLoader({ - scope, - page: pageData.page, - dataSources, - resources, -})} + import type { PageMeta } from "@webstudio-is/sdk"; + ${generateResourcesLoader({ + scope, + page: pageData.page, + dataSources, + resources, + })} -${generatePageMeta({ - globalScope: scope, - page: pageData.page, - dataSources, -})} + ${generatePageMeta({ + globalScope: scope, + page: pageData.page, + dataSources, + })} -${generateFormsProperties(props)} + ${generateFormsProperties(props)} -${generateRemixParams(pageData.page.path)} + ${generateRemixParams(pageData.page.path)} -export const projectId = "${siteData.build.projectId}"; + export const projectId = "${siteData.build.projectId}"; -export const contactEmail = ${JSON.stringify(contactEmail)}; + export const contactEmail = ${JSON.stringify(contactEmail)}; -export const customCode = ${JSON.stringify(projectMeta?.code?.trim() ?? "")}; -`; + export const customCode = ${JSON.stringify( + projectMeta?.code?.trim() ?? "" + )}; + `; /* The _index is mandatory. @@ -662,7 +685,9 @@ export const customCode = ${JSON.stringify(projectMeta?.code?.trim() ?? "")}; const remixRoute = generateRemixRoute(pagePath); const fileName = `${remixRoute}.tsx`; - const routeFileContent = routeFileTemplate + const routeFileContent = ( + documentType === "html" ? routeFileTemplate : routeXmlFileTemplate + ) .replace( /".*\/__generated__\/_index"/, `"../__generated__/${remixRoute}"` @@ -672,17 +697,27 @@ export const customCode = ${JSON.stringify(projectMeta?.code?.trim() ?? "")}; `"../__generated__/${remixRoute}.server"` ); - await ensureFileInPath(join(routesDir, fileName), routeFileContent); - await ensureFileInPath(join(generatedDir, fileName), pageExports); + await createFileIfNotExists(join(routesDir, fileName), routeFileContent); - await ensureFileInPath( + await createFileIfNotExists(join(generatedDir, fileName), pageExports); + + await createFileIfNotExists( join(generatedDir, `${remixRoute}.server.tsx`), serverExports ); } - await writeFile( - join(generatedDir, "[sitemap.xml].ts"), + // MARK: - Implicit sitemap.xml + await createFileIfNotExists( + join(routesDir, "[sitemap.xml]._index.tsx"), + implicitSiteMapTemplate.replace( + /".*\/__generated__\//, + `"../__generated__/` + ) + ); + + await createFileIfNotExists( + join(generatedDir, "$resources.sitemap.xml.ts"), ` export const sitemap = ${JSON.stringify( getStaticSiteMapXml(siteData.build.pages, siteData.build.updatedAt), @@ -702,12 +737,12 @@ export const customCode = ${JSON.stringify(projectMeta?.code?.trim() ?? "")}; const content = `import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime"; -export const loader = (arg: LoaderFunctionArgs) => { - return redirect("${redirect.new}", ${redirect.status ?? 301}); -}; -`; + export const loader = (arg: LoaderFunctionArgs) => { + return redirect("${redirect.new}", ${redirect.status ?? 301}); + }; + `; - await ensureFileInPath(join(routesDir, redirectFileName), content); + await createFileIfNotExists(join(routesDir, redirectFileName), content); } } diff --git a/packages/cli/templates/defaults/app/routes/template.tsx b/packages/cli/templates/defaults/app/route-templates/html-doc-type.tsx similarity index 100% rename from packages/cli/templates/defaults/app/routes/template.tsx rename to packages/cli/templates/defaults/app/route-templates/html-doc-type.tsx diff --git a/packages/cli/templates/defaults/app/route-templates/implicit-sitemap.tsx b/packages/cli/templates/defaults/app/route-templates/implicit-sitemap.tsx new file mode 100644 index 000000000000..b3e25011ab55 --- /dev/null +++ b/packages/cli/templates/defaults/app/route-templates/implicit-sitemap.tsx @@ -0,0 +1,34 @@ +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { sitemap } from "../../../../__generated__/$resources.sitemap.xml"; + +export const loader = (arg: LoaderFunctionArgs) => { + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + + const urls = sitemap.map((page) => { + const url = new URL(`https://${host}${page.path}`); + + return ` + + ${url.href} + ${page.lastModified.split("T")[0]} + + `; + }); + + return new Response( + ` + +${urls.join("")} + + `, + { + headers: { + "Content-Type": "application/xml", + }, + status: 200, + } + ); +}; diff --git a/packages/cli/templates/defaults/app/route-templates/xml-doc-type.tsx b/packages/cli/templates/defaults/app/route-templates/xml-doc-type.tsx new file mode 100644 index 000000000000..45373b6cad57 --- /dev/null +++ b/packages/cli/templates/defaults/app/route-templates/xml-doc-type.tsx @@ -0,0 +1,71 @@ +/* eslint-disable camelcase */ +import { + type ServerRuntimeMetaFunction as MetaFunction, + type LoaderFunctionArgs, + type HeadersFunction, + redirect, +} from "@remix-run/server-runtime"; +import { ReactSdkContext } from "@webstudio-is/react-sdk"; +import { Page } from "../../../../__generated__/_index"; +import { + loadResources, + getPageMeta, + getRemixParams, +} from "../../../../__generated__/_index.server"; + +import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs"; +import { renderToString } from "react-dom/server"; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources({ system }); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + return redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + const text = renderToString( + + + + ); + + return new Response(`\n${text}`); +}; + +export const headers: HeadersFunction = ({ loaderHeaders }) => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + "Content-Type": "application/xml", + }; +}; diff --git a/packages/cli/templates/defaults/app/routes/[sitemap.xml].tsx b/packages/cli/templates/defaults/app/routes/[sitemap.xml].tsx deleted file mode 100644 index a9a3faa696b2..000000000000 --- a/packages/cli/templates/defaults/app/routes/[sitemap.xml].tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { sitemap } from "../__generated__/[sitemap.xml]"; - -export const loader = (arg: LoaderFunctionArgs) => { - const host = - arg.request.headers.get("x-forwarded-host") || - arg.request.headers.get("host") || - ""; - - const urls = sitemap.map((page) => { - const url = new URL(`https://${host}${page.path}`); - - return ` - - ${url.href} - ${page.lastModified.split("T")[0]} - - `; - }); - - return new Response( - ` - -${urls.join("")} - - `, - { - headers: { - "Content-Type": "application/xml", - }, - status: 200, - } - ); -}; diff --git a/packages/sdk-components-react/src/xml-node.tsx b/packages/sdk-components-react/src/xml-node.tsx index ce429e7655f2..57ceeec8017d 100644 --- a/packages/sdk-components-react/src/xml-node.tsx +++ b/packages/sdk-components-react/src/xml-node.tsx @@ -5,6 +5,7 @@ export const defaultTag = "div"; // We don't want to enable all tags because Box is usually a container and we have specific components for many tags. type Props = { tag: string; + xmlns?: string; children: ReactNode; }; diff --git a/packages/sdk/src/resources-generator.ts b/packages/sdk/src/resources-generator.ts index 73d6ad3fdfd0..1634b7733b4c 100644 --- a/packages/sdk/src/resources-generator.ts +++ b/packages/sdk/src/resources-generator.ts @@ -89,7 +89,7 @@ export const generateResourcesLoader = ({ generated += `import { loadResource, isLocalResource, type System } from "@webstudio-is/sdk";\n`; if (hasResources) { - generated += `import { sitemap } from "./[sitemap.xml]";\n`; + generated += `import { sitemap } from "./$resources.sitemap.xml";\n`; } generated += `export const loadResources = async (_props: { system: System }) => {\n`;