diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb99cd2..1a39b5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,16 @@ on: jobs: test: - name: Test on ${{ matrix.os }} + name: Test ${{ matrix.example }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] + example: + - examples/basic + - examples/onBeforeRender + - examples/ssr-spa steps: - uses: actions/checkout@v4 @@ -20,16 +24,10 @@ jobs: - name: Build vike-solid run: pnpm run build - - name: Test building examples/basic + - name: Test building example run: pnpm run build - working-directory: ./examples/basic - - name: Test building examples/ssr-spa - run: pnpm run build - working-directory: ./examples/ssr-spa + working-directory: ./${{ matrix.example }} - - name: Test TypeScript examples/basic - run: pnpm run test - working-directory: ./examples/basic - - name: Test TypeScript examples/ssr-spa + - name: Test TypeScript example run: pnpm run test - working-directory: ./examples/ssr-spa + working-directory: ./${{ matrix.example }} diff --git a/examples/basic/README.md b/examples/basic/README.md index a3fa07a..9b13f9a 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -1,4 +1,10 @@ -Basic example of using `vike-solid`. +Basic example of using `vike-solid`, showcasing: + +- [layouts](https://vike.dev/layouts) +- rendering to `` +- fetching data with [`data()`](https://vike.dev/data) hooks +- [configs](https://vike.dev/config) +- [error pages](https://vike.dev/error-page) ```bash git clone git@github.com:vikejs/vike-solid diff --git a/examples/basic/pages/star-wars/@id/+Page.tsx b/examples/basic/pages/star-wars/@id/+Page.tsx index 017de2a..9efe771 100644 --- a/examples/basic/pages/star-wars/@id/+Page.tsx +++ b/examples/basic/pages/star-wars/@id/+Page.tsx @@ -1,14 +1,16 @@ -import type { MovieDetails } from "../types"; +import type { Data } from "./+data"; +import { useData } from "vike-solid/useData"; -export default function Page(props: { movie: MovieDetails }) { +export default function Page() { + const { movie } = useData(); return ( <> -

{props.movie.title}

- Release Date: {props.movie.release_date} +

{movie.title}

+ Release Date: {movie.release_date}
- Director: {props.movie.director} + Director: {movie.director}
- Producer: {props.movie.producer} + Producer: {movie.producer} ); } diff --git a/examples/basic/pages/star-wars/@id/+data.ts b/examples/basic/pages/star-wars/@id/+data.ts new file mode 100644 index 0000000..f1df58f --- /dev/null +++ b/examples/basic/pages/star-wars/@id/+data.ts @@ -0,0 +1,29 @@ +// https://vike.dev/data +export { data }; +export type { Data }; + +import fetch from "cross-fetch"; +import type { PageContextServer } from "vike/types"; +import { filterMovieData } from "../filterMovieData"; +import type { MovieDetails } from "../types"; + +type Data = Awaited>; + +const data = async (pageContext: PageContextServer) => { + const response = await fetch( + `https://star-wars.brillout.com/api/films/${pageContext.routeParams?.id}.json` + ); + let movie = (await response.json()) as MovieDetails; + + // We remove data we don't need because we pass `pageContext.movie` to + // the client; we want to minimize what is sent over the network. + movie = filterMovieData(movie); + + const { title } = movie; + + return { + movie, + // The page's + title, + }; +}; diff --git a/examples/basic/pages/star-wars/index/+Page.tsx b/examples/basic/pages/star-wars/index/+Page.tsx index 0d36d2f..563ad92 100644 --- a/examples/basic/pages/star-wars/index/+Page.tsx +++ b/examples/basic/pages/star-wars/index/+Page.tsx @@ -1,12 +1,14 @@ import { For } from "solid-js"; -import type { Movie } from "../types"; +import type { Data } from "./+data"; +import { useData } from "vike-solid/useData"; -export default function Page(props: { movies: Movie[] }) { +export default function Page() { + const { movies } = useData<Data>(); return ( <> <h1>Star Wars Movies</h1> <ol> - <For each={props.movies}> + <For each={movies}> {(movie, i) => ( <li> <a href={`/star-wars/${movie.id}`}>{movie.title}</a> ( diff --git a/examples/basic/pages/star-wars/index/+data.ts b/examples/basic/pages/star-wars/index/+data.ts new file mode 100644 index 0000000..f2693a6 --- /dev/null +++ b/examples/basic/pages/star-wars/index/+data.ts @@ -0,0 +1,80 @@ +// https://vike.dev/data +export { data }; +export type { Data }; + +import fetch from "node-fetch"; +//import { filterMovieData } from '../filterMovieData' +import type { Movie, MovieDetails } from "../types"; + +// export { onBeforePrerenderStart } + +type Data = Awaited<ReturnType<typeof data>>; + +const data = async () => { + const movies = await getStarWarsMovies(); + return { + // We remove data we don't need because we pass `pageContext.movies` to + // the client; we want to minimize what is sent over the network. + movies: filterMoviesData(movies), + // The page's <title> + title: getTitle(movies), + }; +}; + +async function getStarWarsMovies(): Promise<MovieDetails[]> { + const response = await fetch("https://star-wars.brillout.com/api/films.json"); + let movies: MovieDetails[] = ((await response.json()) as any).results; + movies = movies.map((movie: MovieDetails, i: number) => ({ + ...movie, + id: String(i + 1), + })); + return movies; +} + +function filterMoviesData(movies: MovieDetails[]): Movie[] { + return movies.map((movie: MovieDetails) => { + const { title, release_date, id } = movie; + return { title, release_date, id }; + }); +} + +/* +async function onBeforePrerenderStart() { + const movies = await getStarWarsMovies() + return [ + { + url: '/star-wars', + // We already provide `pageContext` here so that vike-solid + // will *not* have to call the `data()` hook defined + // above in this file. + pageContext: { + data: { + movies: filterMoviesData(movies), + title: getTitle(movies) + }, + } + }, + ...movies.map((movie) => { + const url = `/star-wars/${movie.id}` + return { + url, + // Note that we can also provide the `pageContext` of other pages. + // This means that vike-solid will not call any + // `data()` hook and the Star Wars API will be called + // only once (in this `onBeforePrerenderStart()` hook). + pageContext: { + data: { + movie: filterMovieData(movie), + title: movie.title + }, + } + } + }) + ] +} +*/ + +function getTitle(movies: Movie[] | MovieDetails[]): string { + const title = `${movies.length} Star Wars Movies`; + return title; +} diff --git a/examples/onBeforeRender/README.md b/examples/onBeforeRender/README.md new file mode 100644 index 0000000..7562bc2 --- /dev/null +++ b/examples/onBeforeRender/README.md @@ -0,0 +1,18 @@ +Basic example of using `vike-solid`, showcasing: + +- [layouts](https://vike.dev/layouts) +- rendering to `<head>` +- fetching data with [`onBeforeRender()`](https://vike.dev/onBeforeRender) hooks +- [configs](https://vike.dev/config) +- [error pages](https://vike.dev/error-page) + +```bash +git clone git@github.com:vikejs/vike-solid +cd vike-solid/examples/basic/ +pnpm install +pnpm run dev +``` + +> **NOTE:** for now `pnpm` is required because of the `workspace:` specifier in +> `package.json`. With this, the example makes use of the local `vike-solid` +> implementation instead of the downloading it from npm. diff --git a/examples/onBeforeRender/assets/logo.svg b/examples/onBeforeRender/assets/logo.svg new file mode 100644 index 0000000..94d3caa --- /dev/null +++ b/examples/onBeforeRender/assets/logo.svg @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="175" height="175" fill="none" version="1.1" viewBox="0 0 175 175" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <metadata> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <defs> + <linearGradient id="linearGradient880" x1="108.64" x2="115.51" y1="88.726" y2="136.2" gradientTransform="matrix(1.0498 0 0 1.0498 -2.9171 -2.9658)" gradientUnits="userSpaceOnUse"> + <stop stop-color="#ffea83" offset="0"/> + <stop stop-color="#FFDD35" offset=".083333"/> + <stop stop-color="#FFA800" offset="1"/> + </linearGradient> + <linearGradient id="paint2_linear" x1="48.975" x2="61.299" y1="3.9232" y2="158.04" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse"> + <stop stop-color="#FFEA83" offset="0"/> + <stop stop-color="#FFDD35" offset=".083333"/> + <stop stop-color="#FFA800" offset="1"/> + </linearGradient> + <linearGradient id="paint0_linear-6" x1="-1.4492" x2="116.62" y1="-5.8123" y2="137.08" gradientTransform="translate(-2.832e-5)" gradientUnits="userSpaceOnUse"> + <stop stop-color="#41D1FF" offset="0"/> + <stop stop-color="#BD34FE" offset="1"/> + </linearGradient> + </defs> + <circle cx="87.5" cy="87.5" r="87.5" fill="#c4c4c4"/> + <circle cx="87.5" cy="87.5" r="87.5" fill="url(#paint0_linear-6)"/> + <g transform="translate(632.92 54.355)" fill="#d38787" stroke-width="1.0614"> + <path d="m-549.75 68.457c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35464-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12956 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.9521c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7178 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2687 8.0366 8.2687 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77686 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91739 0.9174 2.3036 1.668 3.0805 1.668 0.77688 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97108 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.345-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35184 1.3135-0.64655 2.7465-0.65453 3.1843-8e-3 0.43784-0.69682 0.79608-1.5308 0.79608-0.83399 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.244 0.91741 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" style="mix-blend-mode:lighten"/> + <path d="m-552.2 68.911c-5.7533-3.1217-6.1166-5.2295-6.1166-35.489 0-30.458 0.35463-32.448 6.3339-35.54 3.9943-2.0655 24.279-2.2805 26.735-0.28333 0.89718 0.72974 6.7203 6.6637 12.94 13.187l11.309 11.86v19.575c0 18.473-0.12957 19.74-2.3011 22.5-4.0223 5.1136-7.558 5.8565-27.65 5.8099-14.15-0.03287-19.008-0.40294-21.25-1.6191zm42.473-6.3594c2.27-1.59 2.359-2.2909 2.359-18.575v-16.923h-6.952c-12.443 0-16.4-4.0845-16.4-16.93v-7.4828h-8.9464c-6.7179 0-9.3619 0.41549-10.614 1.668-2.5031 2.5031-2.5031 55.724 0 58.228 2.4502 2.4502 37.058 2.4636 40.553 0.01609zm-1.8867-42.165c0-0.16422-2.8659-3.1346-6.3686-6.6008l-6.3686-6.3022v4.9328c0 6.3185 1.8955 8.2688 8.0366 8.2688 2.5854 0 4.7007-0.13434 4.7007-0.29859zm-57.57 44.279c-5.6185-3.0486-6.1166-5.593-6.1166-31.243 0-18.891 0.31331-24.063 1.6101-26.571 1.809-3.4981 6.5048-6.3339 10.489-6.3339 2.4847 0 2.5814 0.19984 1.541 3.1843-0.61054 1.7514-1.7457 3.1843-2.5226 3.1843-0.77687 0-2.1631 0.75059-3.0805 1.668-2.4923 2.4923-2.4923 47.244 0 49.736 0.91741 0.91739 2.3036 1.668 3.0805 1.668 0.77686 0 1.912 1.4329 2.5226 3.1843 1.0562 3.0298 0.97107 3.1822-1.7537 3.1418-1.575-0.02331-4.1713-0.75194-5.7694-1.6191zm-16.983-4.2458c-5.4392-2.9512-6.1166-5.9415-6.1166-26.997 0-15.096 0.34502-19.878 1.6101-22.325 1.7476-3.3796 6.4758-6.3339 10.137-6.3339 1.8666 0 2.1789 0.44955 1.6594 2.3882-0.35182 1.3135-0.64653 2.7465-0.65452 3.1843-8e-3 0.43784-0.69683 0.79608-1.5308 0.79608-0.83397 0-2.2669 0.75059-3.1843 1.668-2.4767 2.4767-2.4767 38.768 0 41.245 0.9174 0.91739 2.2946 1.668 3.0605 1.668 1.196 0 2.6402 2.995 2.6871 5.5726 0.0241 1.3294-4.5804 0.80962-7.6676-0.8655z" fill-opacity=".47466" style="mix-blend-mode:lighten"/> + </g> + <path d="m128.48 88.913-24.027 4.6784c-0.39475 0.07685-0.68766 0.40944-0.71076 0.80849l-1.4782 24.805c-0.0347 0.58371 0.50497 1.0372 1.0792 0.90602l6.6886-1.5338c0.62676-0.14383 1.1916 0.40419 1.0635 1.0299l-1.9874 9.6702c-0.13438 0.65091 0.48084 1.2073 1.1202 1.0142l4.1322-1.2472c0.64041-0.19317 1.2556 0.36535 1.1202 1.0162l-3.158 15.191c-0.19842 0.95011 1.074 1.4677 1.6042 0.653l0.35485-0.54382 19.578-38.827c0.32755-0.64985-0.23727-1.391-0.95641-1.2535l-6.8849 1.3207c-0.6467 0.12389-1.1979-0.47453-1.0152-1.1034l4.4944-15.482c0.18266-0.63012-0.36955-1.2295-1.0173-1.1034z" fill="url(#linearGradient880)" stroke-width="1.0498"/> + <rect x="3" y="3" width="169" height="169" rx="84.5" stroke="url(#paint2_linear)" stroke-width="6" style="mix-blend-mode:soft-light"/> +</svg> diff --git a/examples/onBeforeRender/components/Link.tsx b/examples/onBeforeRender/components/Link.tsx new file mode 100644 index 0000000..361010d --- /dev/null +++ b/examples/onBeforeRender/components/Link.tsx @@ -0,0 +1,16 @@ +import { createMemo } from "solid-js"; +import { usePageContext } from "vike-solid/usePageContext"; + +export function Link(props: { href: string; children: string }) { + const pageContext = usePageContext(); + const isActive = createMemo(() => + props.href === "/" + ? pageContext.urlPathname === props.href + : pageContext.urlPathname.startsWith(props.href) + ); + return ( + <a href={props.href} class={isActive() ? "is-active" : undefined}> + {props.children} + </a> + ); +} diff --git a/examples/onBeforeRender/layouts/LayoutDefault.tsx b/examples/onBeforeRender/layouts/LayoutDefault.tsx new file mode 100644 index 0000000..4c947a2 --- /dev/null +++ b/examples/onBeforeRender/layouts/LayoutDefault.tsx @@ -0,0 +1,73 @@ +import "./style.css"; +import logoUrl from "../assets/logo.svg"; +import { Link } from "../components/Link"; +import type { JSX } from "solid-js"; + +export default function LayoutDefault(props: { children?: JSX.Element }) { + return ( + <div + style={{ + display: "flex", + "max-width": "900px", + margin: "auto", + }} + > + <Sidebar> + <Logo /> + <Link href="/">Welcome</Link> + <Link href="/star-wars">Data Fetching</Link> + </Sidebar> + <Content>{props.children}</Content> + </div> + ); +} + +function Sidebar(props: { children: JSX.Element }) { + return ( + <div + id="sidebar" + style={{ + padding: "20px", + "flex-shrink": 0, + display: "flex", + "flex-direction": "column", + "line-height": "1.8em", + "border-right": "2px solid #eee", + }} + > + {props.children} + </div> + ); +} + +function Content(props: { children: JSX.Element }) { + return ( + <div id="page-container"> + <div + id="page-content" + style={{ + padding: "20px", + "padding-bottom": "50px", + "min-height": "100vh", + }} + > + {props.children} + </div> + </div> + ); +} + +function Logo() { + return ( + <div + style={{ + "margin-top": "20px", + "margin-bottom": "10px", + }} + > + <a href="/"> + <img src={logoUrl} height={64} width={64} /> + </a> + </div> + ); +} diff --git a/examples/onBeforeRender/layouts/style.css b/examples/onBeforeRender/layouts/style.css new file mode 100644 index 0000000..7afa4ca --- /dev/null +++ b/examples/onBeforeRender/layouts/style.css @@ -0,0 +1,29 @@ +/* Links */ +a { + text-decoration: none; +} +#sidebar a { + padding: 2px 10px; + margin-left: -10px; +} +#sidebar a.is-active { + background-color: #eee; +} + +/* Reset */ +body { + margin: 0; + font-family: sans-serif; +} +* { + box-sizing: border-box; +} + +/* Page Transition Anmiation */ +#page-content { + opacity: 1; + transition: opacity 0.3s ease-in-out; +} +body.page-is-transitioning #page-content { + opacity: 0; +} diff --git a/examples/onBeforeRender/package.json b/examples/onBeforeRender/package.json new file mode 100644 index 0000000..94b1638 --- /dev/null +++ b/examples/onBeforeRender/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "scripts": { + "dev": "vike-solid dev", + "build": "vike-solid build", + "preview": "vike-solid preview", + "test": "tsc --noEmit" + }, + "dependencies": { + "cross-fetch": "^3.1.8", + "node-fetch": "^3.3.2", + "solid-js": "^1.7.11", + "vike-solid": "workspace:*", + "vike": "^0.4.148" + }, + "devDependencies": { + "typescript": "^5.1.6" + }, + "type": "module" +} diff --git a/examples/onBeforeRender/pages/+config.h.ts b/examples/onBeforeRender/pages/+config.h.ts new file mode 100644 index 0000000..a316842 --- /dev/null +++ b/examples/onBeforeRender/pages/+config.h.ts @@ -0,0 +1,15 @@ +import type { Config } from "vike/types"; +import vikeSolid from "vike-solid"; +import Layout from "../layouts/LayoutDefault"; +import Head from "./Head"; + +// Default config (can be overridden by pages) +export default { + Layout, + Head, + // <title> + title: "My Vike Solid App", + // <meta name="description"> + description: "Demo showcasing vike-solid", + extends: vikeSolid, +} satisfies Config; diff --git a/examples/onBeforeRender/pages/Head.tsx b/examples/onBeforeRender/pages/Head.tsx new file mode 100644 index 0000000..f56c9d9 --- /dev/null +++ b/examples/onBeforeRender/pages/Head.tsx @@ -0,0 +1,11 @@ +// Default <head> (can be overridden by pages) + +import logoUrl from "../assets/logo.svg"; + +export default function Head() { + return ( + <> + <link rel="icon" href={logoUrl} /> + </> + ); +} diff --git a/examples/onBeforeRender/pages/_error/+Page.tsx b/examples/onBeforeRender/pages/_error/+Page.tsx new file mode 100644 index 0000000..8b384c0 --- /dev/null +++ b/examples/onBeforeRender/pages/_error/+Page.tsx @@ -0,0 +1,18 @@ +export default function Page(props: { is404: boolean; errorInfo?: string }) { + if (props.is404) { + return ( + <> + <h1>404 Page Not Found</h1> + <p>This page could not be found.</p> + <p>{props.errorInfo}</p> + </> + ); + } else { + return ( + <> + <h1>500 Internal Server Error</h1> + <p>Something went wrong.</p> + </> + ); + } +} diff --git a/examples/onBeforeRender/pages/index/+Page.tsx b/examples/onBeforeRender/pages/index/+Page.tsx new file mode 100644 index 0000000..3df5011 --- /dev/null +++ b/examples/onBeforeRender/pages/index/+Page.tsx @@ -0,0 +1,30 @@ +import { ClientOnly } from "vike-solid/ClientOnly"; + +export default function Page() { + return ( + <> + <h1>My vike-solid app</h1> + This page is: + <ul> + <li>Rendered to HTML.</li> + + <ClientOnly load={() => import("./Counter")} fallback={<li>Waiting for client-side only component to load (quick)</li>}> + {(Counter) => <li> + Interactive 1. <Counter /> + </li>} + </ClientOnly> + + <ClientOnly load={async () => { + // Wasting time to show the fallback + await new Promise(resolve => setTimeout(resolve, 2000)); + + return import("./Counter"); + }} fallback={<li>Waiting for client-side only component to load (slow)</li>}> + {(Counter) => <li> + Interactive 2. <Counter /> + </li>} + </ClientOnly> + </ul> + </> + ); +} diff --git a/examples/onBeforeRender/pages/index/Counter.tsx b/examples/onBeforeRender/pages/index/Counter.tsx new file mode 100644 index 0000000..742ab48 --- /dev/null +++ b/examples/onBeforeRender/pages/index/Counter.tsx @@ -0,0 +1,11 @@ +import { createSignal } from "solid-js"; + +export default function Counter() { + const [count, setCount] = createSignal(0); + + return ( + <button onClick={() => setCount((count) => count + 1)}> + Counter {count()} + </button> + ); +} diff --git a/examples/onBeforeRender/pages/star-wars/@id/+Page.tsx b/examples/onBeforeRender/pages/star-wars/@id/+Page.tsx new file mode 100644 index 0000000..017de2a --- /dev/null +++ b/examples/onBeforeRender/pages/star-wars/@id/+Page.tsx @@ -0,0 +1,14 @@ +import type { MovieDetails } from "../types"; + +export default function Page(props: { movie: MovieDetails }) { + return ( + <> + <h1>{props.movie.title}</h1> + Release Date: {props.movie.release_date} + <br /> + Director: {props.movie.director} + <br /> + Producer: {props.movie.producer} + </> + ); +} diff --git a/examples/basic/pages/star-wars/@id/+onBeforeRender.ts b/examples/onBeforeRender/pages/star-wars/@id/+onBeforeRender.ts similarity index 100% rename from examples/basic/pages/star-wars/@id/+onBeforeRender.ts rename to examples/onBeforeRender/pages/star-wars/@id/+onBeforeRender.ts diff --git a/examples/onBeforeRender/pages/star-wars/filterMovieData.ts b/examples/onBeforeRender/pages/star-wars/filterMovieData.ts new file mode 100644 index 0000000..d376801 --- /dev/null +++ b/examples/onBeforeRender/pages/star-wars/filterMovieData.ts @@ -0,0 +1,11 @@ +import type { MovieDetails } from "./types"; + +export { filterMovieData }; + +function filterMovieData( + movie: MovieDetails & Record<string, unknown> +): MovieDetails { + const { id, title, release_date, director, producer } = movie; + movie = { id, title, release_date, director, producer }; + return movie; +} diff --git a/examples/onBeforeRender/pages/star-wars/index/+Page.tsx b/examples/onBeforeRender/pages/star-wars/index/+Page.tsx new file mode 100644 index 0000000..0d36d2f --- /dev/null +++ b/examples/onBeforeRender/pages/star-wars/index/+Page.tsx @@ -0,0 +1,24 @@ +import { For } from "solid-js"; +import type { Movie } from "../types"; + +export default function Page(props: { movies: Movie[] }) { + return ( + <> + <h1>Star Wars Movies</h1> + <ol> + <For each={props.movies}> + {(movie, i) => ( + <li> + <a href={`/star-wars/${movie.id}`}>{movie.title}</a> ( + {movie.release_date}) + </li> + )} + </For> + </ol> + <p> + Source:{" "} + <a href="https://star-wars.brillout.com">star-wars.brillout.com</a>. + </p> + </> + ); +} diff --git a/examples/basic/pages/star-wars/index/+onBeforeRender.ts b/examples/onBeforeRender/pages/star-wars/index/+onBeforeRender.ts similarity index 100% rename from examples/basic/pages/star-wars/index/+onBeforeRender.ts rename to examples/onBeforeRender/pages/star-wars/index/+onBeforeRender.ts diff --git a/examples/onBeforeRender/pages/star-wars/types.ts b/examples/onBeforeRender/pages/star-wars/types.ts new file mode 100644 index 0000000..fd78915 --- /dev/null +++ b/examples/onBeforeRender/pages/star-wars/types.ts @@ -0,0 +1,13 @@ +export type Movie = { + id: string; + title: string; + release_date: string; +}; + +export type MovieDetails = { + id: string; + title: string; + release_date: string; + director: string; + producer: string; +}; diff --git a/examples/onBeforeRender/tsconfig.json b/examples/onBeforeRender/tsconfig.json new file mode 100644 index 0000000..c70b969 --- /dev/null +++ b/examples/onBeforeRender/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "strict": true, + "module": "ES2020", + "moduleResolution": "node", + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "types": ["vike-solid/client"], + "jsx": "preserve", + "jsxImportSource": "solid-js", + "skipLibCheck": true, + "esModuleInterop": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3b7f71..4f89898 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,28 @@ importers: specifier: ^5.1.6 version: 5.3.3 + examples/onBeforeRender: + dependencies: + cross-fetch: + specifier: ^3.1.8 + version: 3.1.8 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + solid-js: + specifier: ^1.7.11 + version: 1.8.7 + vike: + specifier: ^0.4.148 + version: 0.4.153(vite@5.0.10) + vike-solid: + specifier: workspace:* + version: link:../../vike-solid + devDependencies: + typescript: + specifier: ^5.1.6 + version: 5.3.3 + examples/ssr-spa: dependencies: solid-js: @@ -2755,7 +2777,6 @@ packages: sirv: 2.0.3 source-map-support: 0.5.21 vite: 5.0.10(@types/node@18.17.4) - dev: true /vite-plugin-solid@2.8.0(solid-js@1.8.7)(vite@5.0.10): resolution: {integrity: sha512-n5FAm7ZmTl94VWUoiJCgG7bouF2NlC9CA1wY/qbVnkFbYDWk++bFWyNoU48aLJ+lMtzNeYzJypJXOHzFKxL9xA==} diff --git a/vike-solid/components/useData.tsx b/vike-solid/components/useData.tsx new file mode 100644 index 0000000..6a579dd --- /dev/null +++ b/vike-solid/components/useData.tsx @@ -0,0 +1 @@ +export { useData } from "../renderer/PageContextProvider"; diff --git a/vike-solid/package.json b/vike-solid/package.json index 60de308..c1bc5ee 100644 --- a/vike-solid/package.json +++ b/vike-solid/package.json @@ -36,6 +36,7 @@ ".": "./dist/+config.js", "./vite": "./dist/vite-plugin-vike-solid.js", "./usePageContext": "./dist/usePageContext.js", + "./useData": "./dist/useData.js", "./ClientOnly": "./dist/ClientOnly.js", "./renderer/onRenderHtml": "./dist/+onRenderHtml.js", "./renderer/onRenderClient": "./dist/+onRenderClient.js" @@ -54,6 +55,9 @@ "usePageContext": [ "dist/components/usePageContext.d.ts" ], + "useData": [ + "dist/components/useData.d.ts" + ], "ClientOnly": [ "dist/components/ClientOnly.d.ts" ] diff --git a/vike-solid/renderer/+config.ts b/vike-solid/renderer/+config.ts index e131b3b..2548cef 100644 --- a/vike-solid/renderer/+config.ts +++ b/vike-solid/renderer/+config.ts @@ -30,6 +30,8 @@ export default { onRenderHtml: "import:vike-solid/renderer/onRenderHtml:onRenderHtml", onRenderClient: "import:vike-solid/renderer/onRenderClient:onRenderClient", + // TODO/next-major-release: remove pageProps (i.e. tell users to use data() instead of onBeforeRender() to fetch data) + // TODO/next-major-release: remove support for setting title over onBeforeRender() // A page can define an onBeforeRender() hook to be run on the server, which // can fetch data and return it as additional page context. Typically it will // return the page's root Solid component's props and additional data that can @@ -64,7 +66,7 @@ export default { effect: toggleSsrRelatedConfig, }, stream: { - env: { server: true } + env: { server: true }, }, }, } satisfies Config; @@ -104,7 +106,7 @@ declare global { * @default false * */ - stream?: boolean + stream?: boolean; } } } diff --git a/vike-solid/renderer/PageContextProvider.tsx b/vike-solid/renderer/PageContextProvider.tsx index adc08ac..85395e3 100644 --- a/vike-solid/renderer/PageContextProvider.tsx +++ b/vike-solid/renderer/PageContextProvider.tsx @@ -1,5 +1,6 @@ export { PageContextProvider }; export { usePageContext }; +export { useData }; import { useContext, createContext, type JSX } from "solid-js"; import { type Store } from "solid-js/store"; @@ -31,3 +32,13 @@ function usePageContext() { ); return pageContext; } +/** Access `pageContext.data` from any SolidJS component + * + * See + * - https://vike.dev/data + * - https://vike.dev/pageContext-anywhere + */ +function useData<Data>(): Data { + const { data } = usePageContext() as any; + return data; +} diff --git a/vike-solid/renderer/getPageElement.tsx b/vike-solid/renderer/getPageElement.tsx index 13affb2..decf032 100644 --- a/vike-solid/renderer/getPageElement.tsx +++ b/vike-solid/renderer/getPageElement.tsx @@ -26,6 +26,7 @@ function Layout(props: { children: JSX.Element }) { function Page() { const pageContext = usePageContext(); + // TODO/next-major-release: remove pageProps (i.e. tell users to use data() instead of onBeforeRender() to fetch data) return ( <> <Dynamic diff --git a/vike-solid/renderer/getTitle.ts b/vike-solid/renderer/getTitle.ts index 1241ba7..bbc5a94 100644 --- a/vike-solid/renderer/getTitle.ts +++ b/vike-solid/renderer/getTitle.ts @@ -4,9 +4,16 @@ import type { PageContext } from "vike/types"; /** * Get the page's title if defined, either from the additional data fetched by - * the page's onBeforeRender() hook or from the config. + * the page's data() and onBeforeRender() hook or from the config. */ function getTitle(pageContext: PageContext): null | string { + // from data() hook + if (pageContext.data?.title !== undefined) { + return pageContext.data.title; + } + + // TODO/next-major-release: remove support for setting title over onBeforeRender() + // from onBeforeRender() hook if (pageContext.title !== undefined) { return pageContext.title; } diff --git a/vike-solid/renderer/types.ts b/vike-solid/renderer/types.ts index 833fff6..ec97f5d 100644 --- a/vike-solid/renderer/types.ts +++ b/vike-solid/renderer/types.ts @@ -12,11 +12,19 @@ declare global { // to `false` (SPA mode). Page?: Page; - /** Properties of the page's root Solid component. */ + // TODO/next-major-release: remove pageProps (i.e. tell users to use data() instead of onBeforeRender() to fetch data) + /** Properties of the page's root Solid component - e.g. set by onBeforeRender() hook */ pageProps?: Record<string, unknown>; - /** <title>${title}</title> - has precedence over the config */ + // TODO/next-major-release: remove support for setting title over onBeforeRender() + /** <title>${title}</title> - set by onBeforeRender() hook, has precedence over the config */ title?: string; + + // Needed by getTitle() + data?: { + /** <title>${title}</title> - set by data() hook, has precedence over the onBeforeRender() hook */ + title?: string; + }; } } } diff --git a/vike-solid/rollup.config.js b/vike-solid/rollup.config.js index 4ef6097..a0aa877 100644 --- a/vike-solid/rollup.config.js +++ b/vike-solid/rollup.config.js @@ -8,6 +8,7 @@ export default [ "./renderer/+onRenderHtml.tsx", "./renderer/+config.ts", "./components/usePageContext.tsx", + "./components/useData.tsx", "./components/ClientOnly.tsx", "./cli/index.ts", ], @@ -18,6 +19,7 @@ export default [ input: [ "./renderer/+onRenderClient.tsx", "./components/usePageContext.tsx", + "./components/useData.tsx", "./cli/index.ts", ], ssr: false, @@ -27,6 +29,7 @@ export default [ input: [ "./renderer/+config.ts", "./components/usePageContext.tsx", + "./components/useData.tsx", "./components/ClientOnly.tsx", "./vite-plugin-vike-solid.ts", ],