diff --git a/packages/adapter-sveltekit/src/hooks/documentation-read.ts b/packages/adapter-sveltekit/src/hooks/documentation-read.ts index 7e5692ed77..6d41435fec 100644 --- a/packages/adapter-sveltekit/src/hooks/documentation-read.ts +++ b/packages/adapter-sveltekit/src/hooks/documentation-read.ts @@ -32,8 +32,6 @@ export const documentationRead: DocumentationReadHook = async ( dataFileContent = source` import { createClient } from "$lib/prismicio"; - export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -58,8 +56,6 @@ export const documentationRead: DocumentationReadHook = async ( dataFileContent = source` import { createClient } from "$lib/prismicio"; - export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -69,6 +65,10 @@ export const documentationRead: DocumentationReadHook = async ( page, }; } + + export async function entries() { + return [{}] + } `; } diff --git a/packages/adapter-sveltekit/src/hooks/project-init.ts b/packages/adapter-sveltekit/src/hooks/project-init.ts index 8d3963bf23..37892b0557 100644 --- a/packages/adapter-sveltekit/src/hooks/project-init.ts +++ b/packages/adapter-sveltekit/src/hooks/project-init.ts @@ -49,49 +49,12 @@ const createPrismicIOFile = async ({ return; } - let createClientContents: string; - - if (isTypeScriptProject) { - createClientContents = source` - /** - * Creates a Prismic client for the project's repository. The client is used to - * query content from the Prismic API. - * - * @param config - Configuration for the Prismic client. - */ - export const createClient = (config: prismic.ClientConfig = {}) => { - const client = prismic.createClient(repositoryName, { - routes, - ...config, - }); - - return client; - }; - `; - } else { - createClientContents = source` - /** - * Creates a Prismic client for the project's repository. The client is used to - * query content from the Prismic API. - * - * @param {prismic.ClientConfig} config - Configuration for the Prismic client. - */ - export const createClient = (config = {}) => { - const client = prismic.createClient(repositoryName, { - routes, - ...config, - }); - - return client; - }; - `; - } - let contents: string; if (isTypeScriptProject) { contents = source` import * as prismic from "@prismicio/client"; + import { CreateClientConfig, enableAutoPreviews } from '@prismicio/svelte/kit'; import config from "../../slicemachine.config.json"; /** @@ -106,21 +69,38 @@ const createPrismicIOFile = async ({ */ // TODO: Update the routes array to match your project's route structure. const routes: prismic.ClientConfig["routes"] = [ - { - type: "homepage", - path: "/", - }, - { - type: "page", - path: "/:uid", - }, + // Examples: + // { + // type: "homepage", + // path: "/", + // }, + // { + // type: "page", + // path: "/:uid", + // }, ]; - ${createClientContents} + /** + * Creates a Prismic client for the project's repository. The client is used to + * query content from the Prismic API. + * + * @param config - Configuration for the Prismic client. + */ + export const createClient = ({ cookies, ...config }: CreateClientConfig = {}) => { + const client = prismic.createClient(repositoryName, { + routes, + ...config, + }); + + enableAutoPreviews({ client, cookies }); + + return client; + }; `; } else { contents = source` import * as prismic from "@prismicio/client"; + import { enableAutoPreviews } from '@prismicio/svelte/kit'; import config from "../../slicemachine.config.json"; /** @@ -137,17 +117,33 @@ const createPrismicIOFile = async ({ */ // TODO: Update the routes array to match your project's route structure. const routes = [ - { - type: "homepage", - path: "/", - }, - { - type: "page", - path: "/:uid", - }, + // Examples: + // { + // type: "homepage", + // path: "/", + // }, + // { + // type: "page", + // path: "/:uid", + // }, ]; - ${createClientContents} + /** + * Creates a Prismic client for the project's repository. The client is used to + * query content from the Prismic API. + * + * @param {import('@prismicio/svelte/kit').CreateClientConfig} config - Configuration for the Prismic client. + */ + export const createClient = ({ cookies, ...config } = {}) => { + const client = prismic.createClient(repositoryName, { + routes, + ...config, + }); + + enableAutoPreviews({ client, cookies }); + + return client; + }; `; } @@ -200,6 +196,88 @@ const createSliceSimulatorPage = async ({ }); }; +const createPreviewRouteMatcherFile = async ({ + helpers, + options, +}: SliceMachineContext) => { + const extension = await getJSFileExtension({ helpers, options }); + const filename = path.join(`src/params/preview.${extension}`); + + if (await checkHasProjectFile({ filename, helpers })) { + return; + } + + const contents = source` + export function match(param) { + return param === 'preview'; + } + `; + + await writeProjectFile({ + filename, + contents, + format: options.format, + helpers, + }); +}; + +const createPreviewRouteDirectory = async ({ + helpers, + options, +}: SliceMachineContext) => { + const filename = path.join( + "src", + "routes", + "[[preview=preview]]", + "README.md", + ); + + if (await checkHasProjectFile({ filename, helpers })) { + return; + } + + const contents = source` + This directory adds support for optional \`/preview\` routes. Do not remove this directory. + + All routes within this directory will be served using the following URLs: + + - \`/example-route\` (prerendered) + - \`/preview/example-route\` (server-rendered) + + See for more information. + `; + + await writeProjectFile({ + filename, + contents, + format: options.format, + helpers, + }); +}; + +const createRootLayoutServerFile = async ({ + helpers, + options, +}: SliceMachineContext) => { + const extension = await getJSFileExtension({ helpers, options }); + const filename = path.join(`src/routes/+layout.server.${extension}`); + + if (await checkHasProjectFile({ filename, helpers })) { + return; + } + + const contents = source` + export const prerender = "auto"; + `; + + await writeProjectFile({ + filename, + contents, + format: options.format, + helpers, + }); +}; + const modifySliceMachineConfig = async ({ helpers, options, @@ -251,7 +329,7 @@ const upsertSliceLibraryIndexFiles = async ( } await Promise.all( - project.config.libraries?.map(async (libraryID) => { + project.config.libraries.map(async (libraryID) => { await upsertSliceLibraryIndexFile({ libraryID, ...context }); }), ); @@ -267,6 +345,9 @@ export const projectInit: ProjectInitHook = async ( modifySliceMachineConfig(context), createPrismicIOFile(context), createSliceSimulatorPage(context), + createPreviewRouteDirectory(context), + createPreviewRouteMatcherFile(context), + createRootLayoutServerFile(context), ]), ); diff --git a/packages/adapter-sveltekit/test/__snapshots__/plugin-documentation-read.test.ts.snap b/packages/adapter-sveltekit/test/__snapshots__/plugin-documentation-read.test.ts.snap index 68005c2882..3d0c05d84e 100644 --- a/packages/adapter-sveltekit/test/__snapshots__/plugin-documentation-read.test.ts.snap +++ b/packages/adapter-sveltekit/test/__snapshots__/plugin-documentation-read.test.ts.snap @@ -11,8 +11,6 @@ Paste in this code: ~~~js [src/routes/[uid]/+page.server.js] import { createClient } from \\"$lib/prismicio\\"; -export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -68,8 +66,6 @@ Paste in this code: ~~~ts [src/routes/[uid]/+page.server.ts] import { createClient } from \\"$lib/prismicio\\"; -export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -125,8 +121,6 @@ Paste in this code: ~~~js [src/routes/single/+page.server.js] import { createClient } from \\"$lib/prismicio\\"; -export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -136,6 +130,10 @@ export async function load({ params, fetch }) { page, }; } + +export async function entries() { + return [{}]; +} ~~~ ## Create your Single's page component @@ -172,8 +170,6 @@ Paste in this code: ~~~ts [src/routes/single/+page.server.ts] import { createClient } from \\"$lib/prismicio\\"; -export const prerender = true; - export async function load({ params, fetch }) { const client = createClient({ fetch }); @@ -183,6 +179,10 @@ export async function load({ params, fetch }) { page, }; } + +export async function entries() { + return [{}]; +} ~~~ ## Create your Single's page component diff --git a/packages/adapter-sveltekit/test/plugin-project-init.test.ts b/packages/adapter-sveltekit/test/plugin-project-init.test.ts index 752a35852e..6450b13476 100644 --- a/packages/adapter-sveltekit/test/plugin-project-init.test.ts +++ b/packages/adapter-sveltekit/test/plugin-project-init.test.ts @@ -258,6 +258,7 @@ describe("prismicio.js file", () => { expect(contents).toMatchInlineSnapshot(` "import * as prismic from \\"@prismicio/client\\"; + import { enableAutoPreviews } from \\"@prismicio/svelte/kit\\"; import config from \\"../../slicemachine.config.json\\"; /** @@ -274,28 +275,32 @@ describe("prismicio.js file", () => { */ // TODO: Update the routes array to match your project's route structure. const routes = [ - { - type: \\"homepage\\", - path: \\"/\\", - }, - { - type: \\"page\\", - path: \\"/:uid\\", - }, + // Examples: + // { + // type: \\"homepage\\", + // path: \\"/\\", + // }, + // { + // type: \\"page\\", + // path: \\"/:uid\\", + // }, ]; /** * Creates a Prismic client for the project's repository. The client is used to * query content from the Prismic API. * - * @param {prismic.ClientConfig} config - Configuration for the Prismic client. + * @param {import(\\"@prismicio/svelte/kit\\").CreateClientConfig} config - + * Configuration for the Prismic client. */ - export const createClient = (config = {}) => { + export const createClient = ({ cookies, ...config } = {}) => { const client = prismic.createClient(repositoryName, { routes, ...config, }); + enableAutoPreviews({ client, cookies }); + return client; }; " @@ -324,6 +329,7 @@ describe("prismicio.js file", () => { expect(contents).toMatchInlineSnapshot(` "import * as prismic from \\"@prismicio/client\\"; + import { CreateClientConfig, enableAutoPreviews } from \\"@prismicio/svelte/kit\\"; import config from \\"../../slicemachine.config.json\\"; /** @@ -338,14 +344,15 @@ describe("prismicio.js file", () => { */ // TODO: Update the routes array to match your project's route structure. const routes: prismic.ClientConfig[\\"routes\\"] = [ - { - type: \\"homepage\\", - path: \\"/\\", - }, - { - type: \\"page\\", - path: \\"/:uid\\", - }, + // Examples: + // { + // type: \\"homepage\\", + // path: \\"/\\", + // }, + // { + // type: \\"page\\", + // path: \\"/:uid\\", + // }, ]; /** @@ -354,12 +361,17 @@ describe("prismicio.js file", () => { * * @param config - Configuration for the Prismic client. */ - export const createClient = (config: prismic.ClientConfig = {}) => { + export const createClient = ({ + cookies, + ...config + }: CreateClientConfig = {}) => { const client = prismic.createClient(repositoryName, { routes, ...config, }); + enableAutoPreviews({ client, cookies }); + return client; }; " @@ -367,6 +379,204 @@ describe("prismicio.js file", () => { }); }); +describe("preview route directory", () => { + it("creates a preview route directory", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + await expect( + fs.access( + path.join(ctx.project.root, "src", "routes", "[[preview=preview]]"), + ), + ).resolves.not.toThrow(); + }); + + it("includes a README.md file", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join( + ctx.project.root, + "src", + "routes", + "[[preview=preview]]", + "README.md", + ), + "utf8", + ); + + // Ensure the file describes the purpose of the directory. + expect(contents).toMatch(/\/preview/i); + }); + + it("does not overwrite README file if it already exists", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + const filePath = path.join( + ctx.project.root, + "src", + "routes", + "[[preview=preview]]", + "README.md", + ); + const contents = "foo"; + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, contents); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const postHookContents = await fs.readFile(filePath, "utf8"); + + expect(postHookContents).toBe(contents); + }); +}); + +describe("preview route matcher", () => { + it("creates a preview.js file", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "params", "preview.js"), + "utf8", + ); + + expect(contents).toMatchInlineSnapshot(` + "export function match(param) { + return param === \\"preview\\"; + } + " + `); + }); + + it("does not overwrite preview.js file if it already exists", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + const filePath = path.join(ctx.project.root, "src", "params", "preview.js"); + const contents = "foo"; + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, contents); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const postHookContents = await fs.readFile(filePath, "utf8"); + + expect(postHookContents).toBe(contents); + }); + + it("creates a preview.ts file when TypeScript is enabled", async (ctx) => { + ctx.project.config.adapter.options.typescript = true; + const pluginRunner = createSliceMachinePluginRunner({ + project: ctx.project, + nativePlugins: { + [ctx.project.config.adapter.resolve]: adapter, + }, + }); + await pluginRunner.init(); + + const log = vi.fn(); + const installDependencies = vi.fn(); + + await pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "params", "preview.ts"), + "utf8", + ); + + expect(contents).toMatchInlineSnapshot(` + "export function match(param) { + return param === \\"preview\\"; + } + " + `); + }); + + it("formats the file by default", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "params", "preview.js"), + "utf8", + ); + + expect(contents).toBe(prettier.format(contents, { parser: "typescript" })); + }); + + it("does not format the file if formatting is disabled", async (ctx) => { + ctx.project.config.adapter.options.format = false; + const pluginRunner = createSliceMachinePluginRunner({ + project: ctx.project, + nativePlugins: { + [ctx.project.config.adapter.resolve]: adapter, + }, + }); + await pluginRunner.init(); + + // Force unusual formatting to detect that formatting did not happen. + const prettierOptions = { printWidth: 10 }; + await fs.writeFile( + path.join(ctx.project.root, ".prettierrc"), + JSON.stringify(prettierOptions), + ); + + const log = vi.fn(); + const installDependencies = vi.fn(); + + await pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "params", "preview.js"), + "utf8", + ); + + expect(contents).not.toBe( + prettier.format(contents, { + ...prettierOptions, + parser: "typescript", + }), + ); + }); +}); + describe("Slice Simulator route", () => { it("creates a Slice Simulator page file", async (ctx) => { const log = vi.fn(); @@ -498,3 +708,134 @@ describe("Slice Simulator route", () => { ); }); }); + +describe("root layout server file", () => { + it("creates a root layout server file", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "routes", "+layout.server.js"), + "utf8", + ); + + expect(contents).toMatchInlineSnapshot(` + "export const prerender = \\"auto\\"; + " + `); + }); + + it("does not overwrite root layout server file if it already exists", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + const filePath = path.join( + ctx.project.root, + "src", + "routes", + "+layout.server.js", + ); + const contents = "foo"; + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, contents); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const postHookContents = await fs.readFile(filePath, "utf8"); + + expect(postHookContents).toBe(contents); + }); + + it("creates a preview.ts file when TypeScript is enabled", async (ctx) => { + ctx.project.config.adapter.options.typescript = true; + const pluginRunner = createSliceMachinePluginRunner({ + project: ctx.project, + nativePlugins: { + [ctx.project.config.adapter.resolve]: adapter, + }, + }); + await pluginRunner.init(); + + const log = vi.fn(); + const installDependencies = vi.fn(); + + await pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "routes", "+layout.server.ts"), + "utf8", + ); + + expect(contents).toMatchInlineSnapshot(` + "export const prerender = \\"auto\\"; + " + `); + }); + + it("formats the file by default", async (ctx) => { + const log = vi.fn(); + const installDependencies = vi.fn(); + + await ctx.pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "routes", "+layout.server.js"), + "utf8", + ); + + expect(contents).toBe(prettier.format(contents, { parser: "typescript" })); + }); + + it("does not format the file if formatting is disabled", async (ctx) => { + ctx.project.config.adapter.options.format = false; + const pluginRunner = createSliceMachinePluginRunner({ + project: ctx.project, + nativePlugins: { + [ctx.project.config.adapter.resolve]: adapter, + }, + }); + await pluginRunner.init(); + + // Force unusual formatting to detect that formatting did not happen. + const prettierOptions = { printWidth: 10 }; + await fs.writeFile( + path.join(ctx.project.root, ".prettierrc"), + JSON.stringify(prettierOptions), + ); + + const log = vi.fn(); + const installDependencies = vi.fn(); + + await pluginRunner.callHook("project:init", { + log, + installDependencies, + }); + + const contents = await fs.readFile( + path.join(ctx.project.root, "src", "routes", "+layout.server.js"), + "utf8", + ); + + expect(contents).not.toBe( + prettier.format(contents, { + ...prettierOptions, + parser: "typescript", + }), + ); + }); +});