diff --git a/fixtures/webstudio-features/.template/vite.config.ts b/fixtures/webstudio-features/.template/vite.config.ts
index b33951009b77..0249ae1afda2 100644
--- a/fixtures/webstudio-features/.template/vite.config.ts
+++ b/fixtures/webstudio-features/.template/vite.config.ts
@@ -3,7 +3,7 @@ import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
// @ts-ignore
import { dedupeMeta } from "./proxy-emulator/dedupe-meta";
-import { existsSync, readdirSync } from "fs";
+import { existsSync } from "fs";
// @ts-ignore
import path from "path";
// @ts-ignore
diff --git a/fixtures/webstudio-features/.webstudio/data.json b/fixtures/webstudio-features/.webstudio/data.json
index ffdef2f45829..ad9334d05dd2 100644
--- a/fixtures/webstudio-features/.webstudio/data.json
+++ b/fixtures/webstudio-features/.webstudio/data.json
@@ -1,10 +1,10 @@
{
"build": {
- "id": "fce42d58-8a67-4f4b-8427-ea7500132e28",
+ "id": "6f14cdae-073c-4f2c-b535-da3f404f3e36",
"projectId": "cddc1d44-af37-4cb6-a430-d300cf6f932d",
- "version": 477,
- "createdAt": "2025-02-28T14:52:36.082+00:00",
- "updatedAt": "2025-02-28T14:52:36.082+00:00",
+ "version": 479,
+ "createdAt": "2025-03-04T16:25:33.097+00:00",
+ "updatedAt": "2025-03-04T16:25:33.097+00:00",
"pages": {
"meta": {
"siteName": "KittyGuardedZone",
@@ -2867,7 +2867,7 @@
"instanceId": "SVI6fI342JAxCvwsg4Oc6",
"name": "xmlns:xhtml",
"type": "string",
- "value": "http://www.w3.org/1999/xhtml"
+ "value": "http://www.w3.org/TR/xhtml11/xhtml11_schema.html"
}
],
[
diff --git a/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts b/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts
index 7b8438c2278b..3a0bc1fc6f04 100644
--- a/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts
+++ b/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts
@@ -1,26 +1,26 @@
export const sitemap = [
{
path: "/",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
{
path: "/_route_with_symbols_",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
{
path: "/form",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
{
path: "/heading-with-id",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
{
path: "/resources",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
{
path: "/nested/nested-page",
- lastModified: "2025-02-28",
+ lastModified: "2025-03-04",
},
];
diff --git a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx
index aef811376761..bf4b5c88d034 100644
--- a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx
+++ b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx
@@ -4,7 +4,10 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
-import { XmlNode, XmlTime } from "@webstudio-is/sdk-components-react";
+import {
+ XmlNode as XmlNode,
+ XmlTime as XmlTime,
+} from "@webstudio-is/sdk-components-react";
export const siteName = "KittyGuardedZone";
@@ -25,62 +28,54 @@ export const pageFontAssets: FontAsset[] = [];
export const pageBackgroundImageAssets: ImageAsset[] = [];
-const Body = (props: any) => ;
-const Heading = (props: any) => null;
-
const Page = (_props: { system: any }) => {
const system = _props.system;
return (
-
-
- {[
- {
- path: "/",
- lastModified: "2024-05-07",
- },
- {
- path: "/olegs-test",
- lastModified: "2024-05-07",
- },
- ]?.map?.((url: any, index: number) => (
-
-
-
- {`${system?.origin ?? "${ORIGIN}"}${url?.path}`}
-
- {url?.lastModified}
-
+
+ {[
+ {
+ path: "/",
+ lastModified: "2024-05-07",
+ },
+ {
+ path: "/olegs-test",
+ lastModified: "2024-05-07",
+ },
+ ]?.map?.((url: any, index: number) => (
+
+
+
+ {`${system?.origin ?? "${ORIGIN}"}${url?.path}`}
-
- ))}
-
- {"Below is custom section"}
-
-
- {"custom-hand-made-location"}
-
-
+ {url?.lastModified}
+
-
- {"Hello"}
- {"https://webstudio.is"}
+
+ ))}
+
+ {"custom-hand-made-location"}
+
+
+
+ {"Hello"}
+ {"https://webstudio.is"}
-
+
);
};
diff --git a/fixtures/webstudio-features/package.json b/fixtures/webstudio-features/package.json
index 30d942db54c9..e7de728d614d 100644
--- a/fixtures/webstudio-features/package.json
+++ b/fixtures/webstudio-features/package.json
@@ -6,7 +6,7 @@
"dev": "react-router dev",
"cli": "NODE_OPTIONS='--conditions=webstudio --import=tsx' webstudio",
"fixtures:link": "pnpm cli link --link https://p-cddc1d44-af37-4cb6-a430-d300cf6f932d-dot-${BUILDER_HOST:-main.development.webstudio.is}'?authToken=1cdc6026-dd5b-4624-b89b-9bd45e9bcc3d'",
- "fixtures:sync": "pnpm cli sync --buildId fce42d58-8a67-4f4b-8427-ea7500132e28 && pnpm prettier --write ./.webstudio/",
+ "fixtures:sync": "pnpm cli sync --buildId 6f14cdae-073c-4f2c-b535-da3f404f3e36 && pnpm prettier --write ./.webstudio/",
"fixtures:build": "pnpm cli build --template react-router --template ./.template && pnpm prettier --write ./app/ ./package.json ./tsconfig.json"
},
"private": true,
diff --git a/fixtures/webstudio-features/proxy-emulator/dedupe-meta.ts b/fixtures/webstudio-features/proxy-emulator/dedupe-meta.ts
index 0b1a1c4b2e5f..7951329ac846 100644
--- a/fixtures/webstudio-features/proxy-emulator/dedupe-meta.ts
+++ b/fixtures/webstudio-features/proxy-emulator/dedupe-meta.ts
@@ -17,18 +17,19 @@ export const dedupeMeta: Plugin = {
const originalWrite = res.write;
const originalEnd = res.end;
- let body = "";
+ const buffers: Buffer[] = [];
res.write = (chunk) => {
- body += chunk.toString();
+ buffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
return true;
};
res.end = (chunk) => {
if (chunk) {
- body += chunk.toString();
+ buffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
+ const body = Buffer.concat(buffers).toString("utf8");
const response = new Response(body);
const metasSet = new Set();
diff --git a/fixtures/webstudio-features/vite.config.ts b/fixtures/webstudio-features/vite.config.ts
index b33951009b77..0249ae1afda2 100644
--- a/fixtures/webstudio-features/vite.config.ts
+++ b/fixtures/webstudio-features/vite.config.ts
@@ -3,7 +3,7 @@ import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
// @ts-ignore
import { dedupeMeta } from "./proxy-emulator/dedupe-meta";
-import { existsSync, readdirSync } from "fs";
+import { existsSync } from "fs";
// @ts-ignore
import path from "path";
// @ts-ignore
diff --git a/packages/cli/src/prebuild.ts b/packages/cli/src/prebuild.ts
index f8ee7dc3c395..d12b65dc79d8 100644
--- a/packages/cli/src/prebuild.ts
+++ b/packages/cli/src/prebuild.ts
@@ -64,10 +64,6 @@ import { createFramework as createVikeSsgFramework } from "./framework-vike-ssg"
const limit = pLimit(10);
-type ComponentsByPage = {
- [id: Page["id"]]: Set;
-};
-
type SiteDataByPage = {
[id: Page["id"]]: {
page: Page;
@@ -300,10 +296,9 @@ export const prebuild = async (options: {
}
}
- const projectMetas = new Map(
+ const usedMetas = new Map(
Object.entries(coreMetas)
);
- const componentsByPage: ComponentsByPage = {};
const siteDataByPage: SiteDataByPage = {};
const fontAssetsByPage: Record = {};
const backgroundImageAssetsByPage: Record = {};
@@ -369,15 +364,10 @@ export const prebuild = async (options: {
assets: siteData.assets,
};
- componentsByPage[page.id] = new Set();
for (const [_instanceId, instance] of instances) {
- if (isCoreComponent(instance.component)) {
- continue;
- }
- componentsByPage[page.id].add(instance.component);
const meta = metas.get(instance.component);
if (meta) {
- projectMetas.set(instance.component, meta);
+ usedMetas.set(instance.component, meta);
}
}
@@ -480,14 +470,14 @@ export const prebuild = async (options: {
styles: new Map(siteData.build?.styles),
styleSourceSelections: new Map(siteData.build?.styleSourceSelections),
// pass only used metas to not generate unused preset styles
- componentMetas: projectMetas,
+ componentMetas: usedMetas,
assetBaseUrl,
atomic: siteData.build.pages.compiler?.atomicStyles ?? true,
});
await createFileIfNotExists(join(generatedDir, "index.css"), cssText);
- for (const [pageId, pageComponents] of Object.entries(componentsByPage)) {
+ for (const page of Object.values(siteData.pages)) {
const scope = createScope([
// manually maintained list of occupied identifiers
"useState",
@@ -498,6 +488,34 @@ export const prebuild = async (options: {
"_props",
]);
+ const pageData = siteDataByPage[page.id];
+ const instances = new Map(pageData.build.instances);
+ const documentType = page.meta.documentType ?? "html";
+ let rootInstanceId = page.rootInstanceId;
+
+ // cleanup xml markup
+ if (documentType === "xml") {
+ // treat first body child as root
+ const bodyInstance = instances.get(rootInstanceId);
+ if (bodyInstance?.children?.[0].type === "id") {
+ rootInstanceId = bodyInstance.children[0].value;
+ }
+ // remove all unexpected components
+ for (const instance of instances.values()) {
+ if (isCoreComponent(instance.component)) {
+ continue;
+ }
+ if (usedMetas.get(instance.component)?.category === "xml") {
+ continue;
+ }
+ instances.delete(instance.id);
+ }
+ }
+
+ const pageComponents = new Set();
+ for (const instance of instances.values()) {
+ pageComponents.add(instance.component);
+ }
const namespaces = new Map<
string,
Set<[shortName: string, componentName: string]>
@@ -518,61 +536,19 @@ export const prebuild = async (options: {
}
let componentImports = "";
- let xmlPresentationComponents = "";
-
- const pageData = siteDataByPage[pageId];
- const documentType = pageData.page.meta.documentType ?? "html";
-
for (const [namespace, componentsSet] of namespaces.entries()) {
- switch (documentType) {
- case "html":
- {
- const specifiers = Array.from(componentsSet)
- .map(
- ([shortName, component]) =>
- `${shortName} as ${scope.getName(component, shortName)}`
- )
- .join(", ");
- componentImports += `import { ${specifiers} } from "${namespace}";\n`;
- }
- break;
-
- case "xml":
- {
- // In case of xml it's the only component we are supporting
- componentImports = `import { XmlNode, XmlTime } from "@webstudio-is/sdk-components-react";\n`;
-
- xmlPresentationComponents += Array.from(componentsSet)
- .map(([shortName, component]) =>
- scope.getName(component, shortName)
- )
- .filter(
- (scopedName) =>
- scopedName !== "XmlNode" && scopedName !== "XmlTime"
- )
- .map((scopedName) =>
- scopedName === "Body"
- ? // Using