Skip to content

Commit 42af921

Browse files
authored
feat: Add head component (#4751)
## Description Allows Head component Hidden under `headSlotComponent` flag Vercel fixture now have a HTMLRewriter example. Improves development. ## Todo In Next PR - Head Title support - Icons - Allow reset `as` field - [x] - BG is now white always - [x] - It's shown as fixed element to not break layout ## Implementation details This component has more priority above PageSettings meta tags e.g. If some tag exists in PageSettings and in HeadSlot the HeadSlot wins ## Steps for reproduction Create HeadSlot <img width="674" alt="image" src="https://github.com/user-attachments/assets/b41f878e-b8fd-48b4-9204-5f079dc1719b" /> - [x] - Visit https://webstudio-fixture-project-a-0su3o.wstd.work/head-tag check server response. <img width="433" alt="image" src="https://github.com/user-attachments/assets/efb7de0d-c23e-4b0d-b775-170e6b61648e" /> See `og:title` is not duplicated. - [x] - Check Elements Panel <img width="565" alt="image" src="https://github.com/user-attachments/assets/5742c3a8-47b1-4877-9b01-528eb3afffe5" /> See no duplicated. All tags exists. - [x] - Click Home. See no meta tags are in Elements panel from `/head-tag` page - [x] - Click back button <img width="555" alt="image" src="https://github.com/user-attachments/assets/2b166249-5fd0-4d0f-a451-ff3c1b4e62b5" /> See all meta is rendered and is not duplicated. ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 4641b29 commit 42af921

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+3597
-1194
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585
const results = [
8686
await assertSize('./fixtures/ssg/dist/client', 352),
8787
await assertSize('./fixtures/webstudio-remix-netlify-functions/build/client', 440),
88-
await assertSize('./fixtures/webstudio-remix-vercel/build/client', 908),
88+
await assertSize('./fixtures/webstudio-remix-vercel/build/client', 926),
8989
]
9090
for (const result of results) {
9191
if (result.passed) {

apps/builder/app/builder/features/components/components.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ const $metas = computed(
6161
for (const [name, componentMeta] of componentMetas) {
6262
// only set available components from component meta
6363
availableComponents.add(name);
64+
65+
if (
66+
isFeatureEnabled("headSlotComponent") === false &&
67+
name === "HeadSlot"
68+
) {
69+
continue;
70+
}
71+
6472
metas.push({
6573
name,
6674
category: componentMeta.category ?? "hidden",

fixtures/webstudio-cloudflare-template/app/routes/[another-page]._index.tsx

+15-58
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
formIdFieldName,
1717
formBotFieldName,
1818
} from "@webstudio-is/sdk/runtime";
19-
import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
19+
import {
20+
ReactSdkContext,
21+
PageSettingsMeta,
22+
} from "@webstudio-is/react-sdk/runtime";
2023
import {
2124
Page,
2225
siteName,
@@ -117,33 +120,15 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
117120
if (data === undefined) {
118121
return metas;
119122
}
120-
const { pageMeta } = data;
121123

122-
if (data.url) {
123-
metas.push({
124-
property: "og:url",
125-
content: data.url,
126-
});
127-
}
124+
const { pageMeta } = data;
125+
const origin = `https://${data.host}`;
128126

129127
if (pageMeta.title) {
130128
metas.push({ title: pageMeta.title });
131-
132-
metas.push({
133-
property: "og:title",
134-
content: pageMeta.title,
135-
});
136129
}
137130

138-
metas.push({ property: "og:type", content: "website" });
139-
140-
const origin = `https://${data.host}`;
141-
142131
if (siteName) {
143-
metas.push({
144-
property: "og:site_name",
145-
content: siteName,
146-
});
147132
metas.push({
148133
"script:ld+json": {
149134
"@context": "https://schema.org",
@@ -154,42 +139,6 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
154139
});
155140
}
156141

157-
if (pageMeta.excludePageFromSearch) {
158-
metas.push({
159-
name: "robots",
160-
content: "noindex, nofollow",
161-
});
162-
}
163-
164-
if (pageMeta.description) {
165-
metas.push({
166-
name: "description",
167-
content: pageMeta.description,
168-
});
169-
metas.push({
170-
property: "og:description",
171-
content: pageMeta.description,
172-
});
173-
}
174-
175-
if (pageMeta.socialImageAssetName) {
176-
metas.push({
177-
property: "og:image",
178-
content: `https://${data.host}${imageLoader({
179-
src: pageMeta.socialImageAssetName,
180-
// Do not transform social image (not enough information do we need to do this)
181-
format: "raw",
182-
})}`,
183-
});
184-
} else if (pageMeta.socialImageUrl) {
185-
metas.push({
186-
property: "og:image",
187-
content: pageMeta.socialImageUrl,
188-
});
189-
}
190-
191-
metas.push(...pageMeta.custom);
192-
193142
return metas;
194143
};
195144

@@ -323,7 +272,8 @@ export const action = async ({
323272
};
324273

325274
const Outlet = () => {
326-
const { system, resources, url } = useLoaderData<typeof loader>();
275+
const { system, resources, url, pageMeta, host } =
276+
useLoaderData<typeof loader>();
327277
return (
328278
<ReactSdkContext.Provider
329279
value={{
@@ -334,6 +284,13 @@ const Outlet = () => {
334284
>
335285
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
336286
<Page key={url} system={system} />
287+
<PageSettingsMeta
288+
url={url}
289+
pageMeta={pageMeta}
290+
host={host}
291+
siteName={siteName}
292+
imageLoader={imageLoader}
293+
/>
337294
</ReactSdkContext.Provider>
338295
);
339296
};

fixtures/webstudio-cloudflare-template/app/routes/_index.tsx

+15-58
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
formIdFieldName,
1717
formBotFieldName,
1818
} from "@webstudio-is/sdk/runtime";
19-
import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
19+
import {
20+
ReactSdkContext,
21+
PageSettingsMeta,
22+
} from "@webstudio-is/react-sdk/runtime";
2023
import {
2124
Page,
2225
siteName,
@@ -117,33 +120,15 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
117120
if (data === undefined) {
118121
return metas;
119122
}
120-
const { pageMeta } = data;
121123

122-
if (data.url) {
123-
metas.push({
124-
property: "og:url",
125-
content: data.url,
126-
});
127-
}
124+
const { pageMeta } = data;
125+
const origin = `https://${data.host}`;
128126

129127
if (pageMeta.title) {
130128
metas.push({ title: pageMeta.title });
131-
132-
metas.push({
133-
property: "og:title",
134-
content: pageMeta.title,
135-
});
136129
}
137130

138-
metas.push({ property: "og:type", content: "website" });
139-
140-
const origin = `https://${data.host}`;
141-
142131
if (siteName) {
143-
metas.push({
144-
property: "og:site_name",
145-
content: siteName,
146-
});
147132
metas.push({
148133
"script:ld+json": {
149134
"@context": "https://schema.org",
@@ -154,42 +139,6 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
154139
});
155140
}
156141

157-
if (pageMeta.excludePageFromSearch) {
158-
metas.push({
159-
name: "robots",
160-
content: "noindex, nofollow",
161-
});
162-
}
163-
164-
if (pageMeta.description) {
165-
metas.push({
166-
name: "description",
167-
content: pageMeta.description,
168-
});
169-
metas.push({
170-
property: "og:description",
171-
content: pageMeta.description,
172-
});
173-
}
174-
175-
if (pageMeta.socialImageAssetName) {
176-
metas.push({
177-
property: "og:image",
178-
content: `https://${data.host}${imageLoader({
179-
src: pageMeta.socialImageAssetName,
180-
// Do not transform social image (not enough information do we need to do this)
181-
format: "raw",
182-
})}`,
183-
});
184-
} else if (pageMeta.socialImageUrl) {
185-
metas.push({
186-
property: "og:image",
187-
content: pageMeta.socialImageUrl,
188-
});
189-
}
190-
191-
metas.push(...pageMeta.custom);
192-
193142
return metas;
194143
};
195144

@@ -323,7 +272,8 @@ export const action = async ({
323272
};
324273

325274
const Outlet = () => {
326-
const { system, resources, url } = useLoaderData<typeof loader>();
275+
const { system, resources, url, pageMeta, host } =
276+
useLoaderData<typeof loader>();
327277
return (
328278
<ReactSdkContext.Provider
329279
value={{
@@ -334,6 +284,13 @@ const Outlet = () => {
334284
>
335285
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
336286
<Page key={url} system={system} />
287+
<PageSettingsMeta
288+
url={url}
289+
pageMeta={pageMeta}
290+
host={host}
291+
siteName={siteName}
292+
imageLoader={imageLoader}
293+
/>
337294
</ReactSdkContext.Provider>
338295
);
339296
};

fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx

+15-58
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
formIdFieldName,
1717
formBotFieldName,
1818
} from "@webstudio-is/sdk/runtime";
19-
import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
19+
import {
20+
ReactSdkContext,
21+
PageSettingsMeta,
22+
} from "@webstudio-is/react-sdk/runtime";
2023
import {
2124
Page,
2225
siteName,
@@ -117,33 +120,15 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
117120
if (data === undefined) {
118121
return metas;
119122
}
120-
const { pageMeta } = data;
121123

122-
if (data.url) {
123-
metas.push({
124-
property: "og:url",
125-
content: data.url,
126-
});
127-
}
124+
const { pageMeta } = data;
125+
const origin = `https://${data.host}`;
128126

129127
if (pageMeta.title) {
130128
metas.push({ title: pageMeta.title });
131-
132-
metas.push({
133-
property: "og:title",
134-
content: pageMeta.title,
135-
});
136129
}
137130

138-
metas.push({ property: "og:type", content: "website" });
139-
140-
const origin = `https://${data.host}`;
141-
142131
if (siteName) {
143-
metas.push({
144-
property: "og:site_name",
145-
content: siteName,
146-
});
147132
metas.push({
148133
"script:ld+json": {
149134
"@context": "https://schema.org",
@@ -154,42 +139,6 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
154139
});
155140
}
156141

157-
if (pageMeta.excludePageFromSearch) {
158-
metas.push({
159-
name: "robots",
160-
content: "noindex, nofollow",
161-
});
162-
}
163-
164-
if (pageMeta.description) {
165-
metas.push({
166-
name: "description",
167-
content: pageMeta.description,
168-
});
169-
metas.push({
170-
property: "og:description",
171-
content: pageMeta.description,
172-
});
173-
}
174-
175-
if (pageMeta.socialImageAssetName) {
176-
metas.push({
177-
property: "og:image",
178-
content: `https://${data.host}${imageLoader({
179-
src: pageMeta.socialImageAssetName,
180-
// Do not transform social image (not enough information do we need to do this)
181-
format: "raw",
182-
})}`,
183-
});
184-
} else if (pageMeta.socialImageUrl) {
185-
metas.push({
186-
property: "og:image",
187-
content: pageMeta.socialImageUrl,
188-
});
189-
}
190-
191-
metas.push(...pageMeta.custom);
192-
193142
return metas;
194143
};
195144

@@ -323,7 +272,8 @@ export const action = async ({
323272
};
324273

325274
const Outlet = () => {
326-
const { system, resources, url } = useLoaderData<typeof loader>();
275+
const { system, resources, url, pageMeta, host } =
276+
useLoaderData<typeof loader>();
327277
return (
328278
<ReactSdkContext.Provider
329279
value={{
@@ -334,6 +284,13 @@ const Outlet = () => {
334284
>
335285
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
336286
<Page key={url} system={system} />
287+
<PageSettingsMeta
288+
url={url}
289+
pageMeta={pageMeta}
290+
host={host}
291+
siteName={siteName}
292+
imageLoader={imageLoader}
293+
/>
337294
</ReactSdkContext.Provider>
338295
);
339296
};

0 commit comments

Comments
 (0)