@nuxtjs/prismic
Version:
Easily connect your Nuxt application to your content hosted on Prismic
296 lines (289 loc) • 10.9 kB
JavaScript
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 };