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`;