Skip to content

Commit

Permalink
Merge pull request #117 from vikejs/phonzammi/dev
Browse files Browse the repository at this point in the history
feat: add support for checking crawlers/bots & integrate renderToStringAsync()
  • Loading branch information
magne4000 authored Sep 9, 2024
2 parents d9d37e0 + dc83aeb commit 731a4ab
Show file tree
Hide file tree
Showing 17 changed files with 155 additions and 33 deletions.
2 changes: 1 addition & 1 deletion examples/full/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dependencies": {
"node-fetch": "^3.3.2",
"solid-js": "^1.8.21",
"vike": "^0.4.191",
"vike": "^0.4.195",
"vike-solid": "workspace:*"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"dependencies": {
"solid-js": "^1.8.21",
"vike": "^0.4.191",
"vike": "^0.4.195",
"vike-solid": "workspace:*"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions examples/solid-query/.test-dev.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { testRun } from "./.testRun";
testRun("pnpm run dev");
2 changes: 2 additions & 0 deletions examples/solid-query/.test-preview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { testRun } from "./.testRun";
testRun("pnpm run preview");
70 changes: 70 additions & 0 deletions examples/solid-query/.testRun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export { testRun };

import { test, expect, run, page, partRegex, getServerUrl, autoRetry } from "@brillout/test-e2e";
const dataHk = partRegex`data-hk=${/[0-9-]+/}`;

function testRun(cmd: `pnpm run ${"dev" | "preview"}`) {
run(cmd);

const content = "Return of the Jedi";
const loading = "Loading movies...";
const titleDefault = "My Vike + Solid App";
const titleOverriden = "6 Star Wars movies";
const titleAsScript = `<script>document.title = "${titleOverriden}"</script>`;
const description = partRegex`<meta ${dataHk} name="description" content="List of 6 Star Wars movies.">`;
test("HTML (as user)", async () => {
const html = await fetchAsUser("/");
expect(html).toContain(content);
expect(html).toContain(loading);
expect(html).toContain(titleAsScript);
expect(getTitle(html)).toBe(titleDefault);
expect(html.split("<title>").length).toBe(2);
expect(html).not.toMatch(description);
});
test("HTML (as bot)", async () => {
const html = await fetchAsBot("/");
expect(html).toContain(content);
expect(html).not.toContain(loading);
expect(html).not.toContain(titleAsScript);
expect(getTitle(html)).toBe(titleOverriden);
expect(html.split("<title>").length).toBe(2);
expect(html).toMatch(description);
});
test("DOM", async () => {
await page.goto(getServerUrl() + "/");
const body = await page.textContent("body");
// Playwright seems to await the HTML stream
expect(body).not.toContain(loading);
expect(body).toContain(content);
await testCounter();
});
}

function getTitle(html: string) {
const title = html.match(/<title>(.*?)<\/title>/i)?.[1];
return title;
}

async function testCounter() {
// autoRetry() for awaiting client-side code loading & executing
await autoRetry(
async () => {
expect(await page.textContent("button")).toBe("Counter 0");
await page.click("button");
expect(await page.textContent("button")).toContain("Counter 1");
},
{ timeout: 5 * 1000 },
);
}

async function fetchAsBot(pathname: string) {
return await fetchHtml(pathname, "curl/8.5.0");
}
async function fetchAsUser(pathname: string) {
return await fetchHtml(pathname, "chrome");
}
async function fetchHtml(pathname: string, userAgent: string) {
const response = await fetch(getServerUrl() + pathname, { headers: { ["User-Agent"]: userAgent } });
const html = await response.text();
return html;
}
2 changes: 1 addition & 1 deletion examples/solid-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@tanstack/solid-query": "5.52.2",
"node-fetch": "^3.3.2",
"solid-js": "^1.8.22",
"vike": "^0.4.191",
"vike": "^0.4.195",
"vike-solid": "workspace:^",
"vike-solid-query": "workspace:^"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/solid-query/pages/+config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export default {

passToClient: ["routeParams"],
stream: true,
injectScriptsAt: "STREAM",
injectScriptsAt: "HTML_STREAM",
extends: [vikeSolid, vikeSolidQuery],
} satisfies Config;
2 changes: 1 addition & 1 deletion examples/solid-query/pages/index/Movies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function Movies() {
};

return (
<QueryBoundary query={query} loadingFallback={<p>Loading movies ...</p>}>
<QueryBoundary query={query} loadingFallback={"Loading movies..."}>
{(movies) => (
<>
<Config title={`${movies.length} Star Wars movies`} />
Expand Down
6 changes: 3 additions & 3 deletions packages/vike-solid-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"peerDependencies": {
"@tanstack/solid-query": ">=5.0.0",
"solid-js": "^1.8.7",
"vike-solid": ">=0.7.3"
"vike-solid": ">=0.7.4"
},
"devDependencies": {
"@brillout/release-me": "^0.4.0",
Expand All @@ -26,8 +26,8 @@
"solid-js": "^1.8.22",
"tsup": "^8.2.4",
"typescript": "^5.5.4",
"vike": "^0.4.193",
"vike-solid": "^0.7.3",
"vike": "^0.4.195",
"vike-solid": "^0.7.4",
"vite": "5.4.2"
},
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/vike-solid/+config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ssrEffect } from "./integration/ssrEffect.js";
const config = {
name: "vike-solid",
require: {
vike: ">=0.4.191",
vike: ">=0.4.195",
},

// https://vike.dev/onRenderHtml
Expand Down
19 changes: 17 additions & 2 deletions packages/vike-solid/hooks/useConfig/useConfig-server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { useConfig };
import type { PageContext } from "vike/types";
import type { PageContextInternal } from "../../types/PageContext.js";
import type { ConfigFromHook } from "../../types/Config.js";
import type { ConfigFromHook, Stream } from "../../types/Config.js";
import { usePageContext } from "../usePageContext.js";
import { getPageContext } from "vike/getPageContext";
import { objectKeys } from "../../utils/objectKeys.js";
Expand All @@ -20,7 +20,14 @@ function useConfig(): (config: ConfigFromHook) => void {

// Component
pageContext = usePageContext();
return (config: ConfigFromHook) => setPageContextConfigFromHook(config, pageContext);
return (config: ConfigFromHook) => {
if (!pageContext._headAlreadySet) {
setPageContextConfigFromHook(config, pageContext);
} else {
// <head> already sent to the browser => send DOM-manipulating scripts during HTML streaming
apply(config, pageContext._stream!);
}
};
}

const configsClientSide = ["title"];
Expand All @@ -44,3 +51,11 @@ function setPageContextConfigFromHook(config: ConfigFromHook, pageContext: PageC
}
});
}

function apply(config: ConfigFromHook, stream: Stream) {
const { title } = config;
if (title) {
const htmlSnippet = `<script>document.title = ${JSON.stringify(title)}</script>`;
stream.write(htmlSnippet);
}
}
29 changes: 23 additions & 6 deletions packages/vike-solid/integration/onRenderHtml.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// https://vike.dev/onRenderHtml
import { generateHydrationScript, renderToStream, renderToString } from "solid-js/web";
import { generateHydrationScript, renderToStream, renderToString, renderToStringAsync } from "solid-js/web";
import { dangerouslySkipEscape, escapeInject, stampPipe } from "vike/server";
import { getHeadSetting } from "./getHeadSetting.js";
import { getPageElement } from "./getPageElement.js";
Expand All @@ -10,6 +10,7 @@ import type { PageContextInternal } from "../types/PageContext.js";
import type { Head } from "../types/Config.js";
import type { JSX } from "solid-js/jsx-runtime";
import { isCallable } from "../utils/isCallable.js";
import isBot from "isbot-fast";

export { onRenderHtml };

Expand All @@ -18,7 +19,7 @@ type TPipe = Parameters<typeof stampPipe>[0];
const onRenderHtml: OnRenderHtmlAsync = async (
pageContext: PageContextServer & PageContextInternal,
): ReturnType<OnRenderHtmlAsync> => {
const pageHtml = getPageHtml(pageContext);
const pageHtml = await getPageHtml(pageContext);

const headHtml = getHeadHtml(pageContext);

Expand All @@ -40,16 +41,32 @@ const onRenderHtml: OnRenderHtmlAsync = async (
</html>`;
};

function getPageHtml(pageContext: PageContextServer) {
async function getPageHtml(pageContext: PageContextServer & PageContextInternal) {
let pageHtml: string | ReturnType<typeof dangerouslySkipEscape> | TPipe = "";
const userAgent: string | undefined =
pageContext.headers?.["user-agent"] ||
// TODO/eventually: remove old way of acccessing the User Agent header.
// @ts-ignore
pageContext.userAgent;

if (pageContext.Page) {
if (!pageContext.config.stream) {
if (userAgent && isBot(userAgent)) {
pageHtml = dangerouslySkipEscape(await renderToStringAsync(() => getPageElement(pageContext)));
} else if (!pageContext.config.stream) {
pageHtml = dangerouslySkipEscape(renderToString(() => getPageElement(pageContext)));
} else if (pageContext.config.stream === "web") {
pageHtml = renderToStream(() => getPageElement(pageContext)).pipeTo;
pageHtml = renderToStream(() => getPageElement(pageContext), {
onCompleteShell(info) {
pageContext._stream ??= info;
},
}).pipeTo;
stampPipe(pageHtml, "web-stream");
} else {
pageHtml = renderToStream(() => getPageElement(pageContext)).pipe;
pageHtml = renderToStream(() => getPageElement(pageContext), {
onCompleteShell(info) {
pageContext._stream ??= info;
},
}).pipe;
stampPipe(pageHtml, "node-stream");
}
}
Expand Down
5 changes: 3 additions & 2 deletions packages/vike-solid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"release:commit": "LANG=en_US release-me commit"
},
"dependencies": {
"isbot-fast": "^1.2.0",
"vite-plugin-solid": "^2.10.2"
},
"peerDependencies": {
"solid-js": "^1.8.7",
"vike": ">=0.4.191",
"vike": ">=0.4.195",
"vite": ">=5.0.0"
},
"devDependencies": {
Expand All @@ -34,7 +35,7 @@
"solid-js": "^1.8.22",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"vike": "^0.4.193",
"vike": "^0.4.195",
"vite": "^5.4.2"
},
"exports": {
Expand Down
1 change: 1 addition & 0 deletions packages/vike-solid/types/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,4 @@ export type ConfigFromHook = PickWithoutGetter<
>;
export type ConfigFromHookResolved = Omit<ConfigFromHook, ConfigsCumulative> &
Pick<Vike.ConfigResolved, ConfigsCumulative>;
export type Stream = { write: (v: string) => void };
3 changes: 2 additions & 1 deletion packages/vike-solid/types/PageContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JSX } from "solid-js";
import type { ConfigFromHookResolved } from "./Config";
import type { ConfigFromHookResolved, Stream } from "./Config";

// https://vike.dev/pageContext#typescript
declare global {
Expand All @@ -15,4 +15,5 @@ declare global {
export type PageContextInternal = {
_configFromHook?: ConfigFromHookResolved;
_headAlreadySet?: boolean;
_stream?: Stream;
};
4 changes: 4 additions & 0 deletions packages/vike-solid/types/isBot.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "isbot-fast" {
function isBot(userAgent: string): boolean;
export = isBot;
}
35 changes: 22 additions & 13 deletions pnpm-lock.yaml

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

0 comments on commit 731a4ab

Please sign in to comment.