UNPKG

@nuxtjs/prismic

Version:

Easily connect your Nuxt application to your content hosted on Prismic

296 lines (289 loc) 10.9 kB
import { join } from 'node:path'; import { readFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { defu } from 'defu'; import { useLogger, defineNuxtModule, getNuxtVersion, createResolver, addPlugin, addComponent, addImports, extendPages, addTemplate } from '@nuxt/kit'; import * as prismicVue from '@prismicio/vue'; import { onDevToolsInitialized, extendServerRpc, startSubprocess } from '@nuxt/devtools-kit'; import { resolve } from 'pathe'; import terminate from 'terminate'; const RPC_NAMESPACE = "prismic-slicemachine-rpc"; var SliceMachineStatus = /* @__PURE__ */ ((SliceMachineStatus2) => { SliceMachineStatus2[SliceMachineStatus2["STARTED"] = 0] = "STARTED"; SliceMachineStatus2[SliceMachineStatus2["STOPPED"] = 1] = "STOPPED"; return SliceMachineStatus2; })(SliceMachineStatus || {}); const DEVTOOLS_UI_ROUTE = "/__prismic-client"; const DEVTOOLS_UI_LOCAL_PORT = 5173; let subProcess = null; const stopSubprocess = () => { if (subProcess) { const pid = subProcess.getProcess().pid; if (pid) { terminate(pid); } subProcess.terminate(); subProcess = null; } }; const setupDevToolsUI = (nuxt, resolver) => { const clientPath = resolver.resolve("./client"); const isProductionBuild = existsSync(clientPath); if (isProductionBuild) { nuxt.hook("vite:serverCreated", async (server) => { const sirv = await import('sirv').then((r) => r.default || r); server.middlewares.use( DEVTOOLS_UI_ROUTE, sirv(clientPath, { dev: true, single: true }) ); }); } else { nuxt.hook("vite:extendConfig", (config) => { config.server = config.server || {}; config.server.proxy = config.server.proxy || {}; config.server.proxy[DEVTOOLS_UI_ROUTE] = { target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`, changeOrigin: true, followRedirects: true, ws: true, rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "") }; }); } nuxt.hooks.hook("close", () => { stopSubprocess(); }); process.on("exit", () => { stopSubprocess(); }); onDevToolsInitialized(() => { const rpc = extendServerRpc( RPC_NAMESPACE, { async getSlicemachineConfig() { const configPath = resolve(nuxt.options.rootDir, "slicemachine.config.json"); if (existsSync(configPath)) { return JSON.parse(await readFile(configPath, "utf-8")); } return null; }, isSliceMachineStarted() { return subProcess !== null; }, startSliceMachine() { stopSubprocess(); subProcess = startSubprocess({ command: "npx", args: ["start-slicemachine"], cwd: nuxt.options.rootDir }, { id: "slicemachine", name: "SliceMachine", icon: "i-simple-icons-prismic" }, nuxt); rpc.broadcast.updateStatus(SliceMachineStatus.STARTED); return SliceMachineStatus.STARTED; }, stopSliceMachine() { stopSubprocess(); rpc.broadcast.updateStatus(SliceMachineStatus.STOPPED); return SliceMachineStatus.STOPPED; } } ); }); nuxt.hook("devtools:customTabs", (tabs) => { tabs.push({ // unique identifier name: "prismic", // title to display in the tab title: "Prismic", // any icon from Iconify, or a URL to an image icon: "i-simple-icons-prismic", // iframe view view: { type: "iframe", src: DEVTOOLS_UI_ROUTE } }); }); }; const logger = useLogger("nuxt:prismic"); const fileExists = (path, extensions = ["js", "ts"]) => { if (!path) { return null; } else if (existsSync(path)) { return path; } const extension = extensions.find((extension2) => existsSync(`${path}.${extension2}`)); return extension ? `${path}.${extension}` : null; }; const module = defineNuxtModule({ meta: { name: "@nuxtjs/prismic", configKey: "prismic", compatibility: { nuxt: ">=3.7.0" } }, defaults: (nuxt) => { let prismicFiles = { client: "~/app/prismic/client", linkResolver: "~/app/prismic/linkResolver", richTextSerializer: "~/app/prismic/richTextSerializer" }; let prismicComponentsFiles = { linkRel: "~/app/prismic/linkRel", richTextComponents: "~/app/prismic/richTextComponents", sliceZoneDefaultComponent: "~/app/prismic/sliceZoneDefaultComponent" }; if (nuxt.options?.future?.compatibilityVersion === 4 || getNuxtVersion(nuxt).startsWith("4")) { prismicFiles = { client: "~/prismic/client", linkResolver: "~/prismic/linkResolver", richTextSerializer: "~/prismic/richTextSerializer" }; prismicComponentsFiles = { linkRel: "~/prismic/linkRel", richTextComponents: "~/prismic/richTextComponents", sliceZoneDefaultComponent: "~/prismic/sliceZoneDefaultComponent" }; } return { endpoint: "", environment: "", clientConfig: {}, ...prismicFiles, injectComponents: true, components: prismicComponentsFiles, preview: "/preview", toolbar: true, devtools: true }; }, hooks: {}, setup(options, nuxt) { nuxt.options.runtimeConfig.public ||= {}; const moduleOptions = defu(nuxt.options.runtimeConfig.public.prismic, options); nuxt.options.runtimeConfig.public.prismic = moduleOptions; const resolver = createResolver(import.meta.url); if (nuxt.options.devtools && options.devtools) { setupDevToolsUI(nuxt, resolver); } const proxyUserFileWithUndefinedFallback = (filename, path, deprecated) => { const resolvedFilename = `prismic/proxy/${filename}.ts`; const resolvedPath = path.replace(/^(~~|@@)/, nuxt.options.rootDir).replace(/^(~|@)/, nuxt.options.srcDir); const maybeUserFile = fileExists(resolvedPath, ["js", "mjs", "ts", "vue"]); if (maybeUserFile) { logger.info(`Using user-defined \`${filename}\` at \`${maybeUserFile.replace(nuxt.options.srcDir, "~").replace(nuxt.options.rootDir, "~~").replace(/\\/g, "/")}\``); if (deprecated) { logger.warn(`\`${filename}\` is deprecated and will be removed in a future version.${typeof deprecated === "string" ? ` ${deprecated}` : ""}`); } addTemplate({ filename: resolvedFilename, getContents: () => `export { default } from '${path}'` }); return true; } else { addTemplate({ filename: resolvedFilename, getContents: () => "export default undefined" }); return false; } }; const proxiedUserClient = proxyUserFileWithUndefinedFallback("client", moduleOptions.client); if (!moduleOptions.endpoint && !proxiedUserClient && !process.env.NUXT_PUBLIC_PRISMIC_ENDPOINT) { logger.warn(`\`endpoint\` option is missing and \`${moduleOptions.client}\` was not found. At least one of them is required for the module to run. Disabling module...`); return; } proxyUserFileWithUndefinedFallback("linkResolver", moduleOptions.linkResolver); proxyUserFileWithUndefinedFallback("richTextSerializer", moduleOptions.richTextSerializer, "Use `components.richTextComponents` instead."); proxyUserFileWithUndefinedFallback("linkRel", moduleOptions.components.linkRel); proxyUserFileWithUndefinedFallback("richTextComponents", moduleOptions.components.richTextComponents); proxyUserFileWithUndefinedFallback("sliceZoneDefaultComponent", moduleOptions.components.sliceZoneDefaultComponent); nuxt.options.build.transpile.push(resolver.resolve("runtime"), "@nuxtjs/prismic", "@prismicio/vue"); nuxt.options.vite.optimizeDeps ||= {}; nuxt.options.vite.optimizeDeps.exclude ||= []; nuxt.options.vite.optimizeDeps.exclude.push("@prismicio/vue"); addPlugin(resolver.resolve("runtime/plugin")); addPlugin(resolver.resolve("runtime/plugin.client")); if (moduleOptions.injectComponents) { [ "PrismicEmbed", "PrismicImage", "PrismicLink", "PrismicText", "PrismicRichText", "PrismicTable", "SliceZone" ].forEach((component) => { addComponent({ name: component, export: component, filePath: "@prismicio/vue" }); }); } const prismicVueAutoImports = Object.keys(prismicVue).filter((key) => key.startsWith("use")).concat( "getSliceComponentProps", "defineSliceZoneComponents", "getRichTextComponentProps", "getTableComponentProps" ).map((key) => { return { name: key, as: key, from: "@prismicio/vue" }; }); addImports(prismicVueAutoImports); addImports({ name: "usePrismicPreview", as: "usePrismicPreview", from: resolver.resolve("runtime/usePrismicPreview") }); if (moduleOptions.preview) { const maybeUserPreviewPage = fileExists(join(nuxt.options.srcDir, nuxt.options.dir.pages, moduleOptions.preview), ["js", "ts", "vue"]); if (maybeUserPreviewPage) { logger.info(`Using user-defined preview page at \`${maybeUserPreviewPage.replace(join(nuxt.options.srcDir), "~").replace(nuxt.options.rootDir, "~~").replace(/\\/g, "/")}\`, available at \`${moduleOptions.preview}\``); } else { logger.info(`Using default preview page, available at \`${moduleOptions.preview}\``); extendPages((pages) => { pages.unshift({ name: "prismic-preview", path: moduleOptions.preview, // Checked before file: resolver.resolve("runtime/PrismicPreview.vue") }); }); } if (!moduleOptions.toolbar) { logger.warn("`toolbar` option is disabled but `preview` is enabled. Previews won't work unless you manually load the toolbar."); } } nuxt.hook("eslint:config:addons", (addons) => { addons.push({ name: "@nuxtjs/prismic", async getConfigs() { const configPath = resolver.resolve(nuxt.options.rootDir, "slicemachine.config.json"); const configs = []; try { if (existsSync(configPath)) { const config = JSON.parse(await readFile(configPath, "utf-8")); if (config && "libraries" in config && Array.isArray(config.libraries)) { configs.push(JSON.stringify({ files: config.libraries.map((library) => `${library.replace("./", "")}/**/index.vue`), rules: { "vue/multi-word-component-names": "off" } })); } } } catch { } return { configs }; } }); }); } }); export { module as default };