Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
504 changes: 499 additions & 5 deletions fixtures/webstudio-remix-vercel/.webstudio/data.json

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions fixtures/webstudio-remix-vercel/app/__generated__/index.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 68 additions & 23 deletions fixtures/webstudio-remix-vercel/app/routes/[sitemap.xml]._index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,79 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { renderToString } from "react-dom/server";
import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { isLocalResource, loadResources } from "@webstudio-is/sdk";
import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
import { Page } from "../__generated__/[sitemap.xml]._index";
import {
getPageMeta,
getRemixParams,
getResources,
} from "../__generated__/[sitemap.xml]._index.server";
import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs";
import { sitemap } from "../__generated__/$resources.sitemap.xml";

export const loader = (arg: LoaderFunctionArgs) => {
const customFetch: typeof fetch = (input, init) => {
if (typeof input !== "string") {
return fetch(input, init);
}

if (isLocalResource(input, "sitemap.xml")) {
// @todo: dynamic import sitemap ???
const response = new Response(JSON.stringify(sitemap));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}

return fetch(input, init);
};

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 urls = sitemap.map((page) => {
const url = new URL(`https://${host}${page.path}`);
const params = getRemixParams(arg.params);

return `
<url>
<loc>${url.href}</loc>
<lastmod>${page.lastModified.split("T")[0]}</lastmod>
</url>
`;
});
const system = {
params,
search: Object.fromEntries(url.searchParams),
origin: url.origin,
};

return new Response(
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join("")}
</urlset>
`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
}
const resources = await loadResources(
customFetch,
getResources({ system }).data
);
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(
<ReactSdkContext.Provider
value={{
imageLoader,
assetBaseUrl,
imageBaseUrl,
resources,
}}
>
<Page system={system} />
</ReactSdkContext.Provider>
);

return new Response(`<?xml version="1.0" encoding="UTF-8"?>\n${text}`, {
headers: { "Content-Type": "application/xml" },
});
};
2 changes: 1 addition & 1 deletion fixtures/webstudio-remix-vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"typecheck": "tsc",
"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 6876de3a-942a-466b-9761-ad7cb2fa2c21 && pnpm prettier --write ./.webstudio/",
"fixtures:sync": "pnpm cli sync --buildId 1710e6a3-6f3b-44c9-8e4f-4176be77673e && pnpm prettier --write ./.webstudio/",
"fixtures:build": "pnpm cli build --template vercel --template internal --preview && pnpm prettier --write ./app/ ./package.json ./tsconfig.json"
},
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/prebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ export const prebuild = async (options: {
.map((scopedName) =>
scopedName === "Body"
? `const ${scopedName} = (props: any) => props.children;`
: `const ${scopedName} = () => null;`
: `const ${scopedName} = (props: any) => null;`
)
.join("\n");
}
Expand Down
39 changes: 27 additions & 12 deletions packages/sdk-components-react/src/xml-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export const defaultTag = "div";
type Props = {
tag: string;
xmlns?: string;
children: ReactNode;
children?: ReactNode;
rel?: string;
hreflang?: string;
href?: string;
"xmlns:xhtml"?: string;
};

export const XmlNode = forwardRef<ElementRef<"div">, Props>(
Expand All @@ -34,29 +38,40 @@ export const XmlNode = forwardRef<ElementRef<"div">, Props>(
return createElement(tag, attrProps, children);
}

const isTextChild = Children.toArray(children).every(
(child) => typeof child === "string"
);
const childrenArray = Children.toArray(children);
const isTextChild =
childrenArray.length > 0 &&
childrenArray.every((child) => typeof child === "string");

const elementName = tag
// Must start from letter or underscore
.replace(/^[^\p{L}_]+/u, "")
// Clear all non letter, number, underscore, dot, and dash
.replaceAll(/[^\p{L}\p{N}\-._]+/gu, "");
.replaceAll(/[^\p{L}\p{N}\-._:]+/gu, "");

const attributes = attributeEntries.map(
([key, value]) => `${key}=${JSON.stringify(value)}`
);

return (
<div style={{ display: isTextChild ? "flex" : "contents" }} {...props}>
<div style={{ color: "rgb(16, 23, 233)" }}>
<div {...props}>
<span style={{ color: "rgb(16, 23, 233)" }}>
&lt;{[elementName, ...attributes].join(" ")}&gt;
</div>
<div ref={ref} style={{ marginLeft: isTextChild ? 0 : "1rem" }}>
{children}
</div>
<div style={{ color: "rgb(16, 23, 233)" }}>&lt;/{elementName}&gt;</div>
</span>
{childrenArray.length > 0 && (
<div
ref={ref}
style={{
display: isTextChild ? "inline" : "block",
marginLeft: isTextChild ? 0 : "1rem",
}}
>
{children}
</div>
)}
<span style={{ color: "rgb(16, 23, 233)" }}>
&lt;/{elementName}&gt;
</span>
</div>
);
}
Expand Down