@nuxtjs/sanity
Version:
Sanity integration for Nuxt
282 lines (278 loc) • 11.5 kB
JavaScript
import { fileURLToPath } from 'node:url';
import crypto from 'node:crypto';
import { existsSync } from 'node:fs';
import { createJiti } from 'jiti';
import { createRegExp, exactly } from 'magic-regexp';
import { useLogger, defineNuxtModule, resolvePath, addTemplate, addPlugin, isNuxtMajorVersion, addImports, addComponentsDir, addServerHandler } from '@nuxt/kit';
import { colors } from 'consola/utils';
import { resolve, relative, join } from 'pathe';
import { defu } from 'defu';
import { genExport } from 'knitwork';
const name = "@nuxtjs/sanity";
const version = "1.14.1";
const logger = useLogger("@nuxtjs/sanity");
const CONFIG_KEY = "sanity";
const module = defineNuxtModule({
meta: {
name,
version,
configKey: CONFIG_KEY,
compatibility: {
nuxt: ">=3.7.0",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TODO: remove when we update to v4
bridge: true
}
},
defaults: {
additionalClients: {},
apiVersion: "1",
disableSmartCdn: false,
perspective: "raw",
withCredentials: false,
configFile: "~~/cms/sanity.config"
},
async setup(options, nuxt) {
if (!options.projectId || !options.dataset) {
const sanityConfigPath = await resolvePath(options.configFile) || /* backwards compatibility */
resolve(nuxt.options.rootDir, "./sanity.json");
const relativeSanityConfigPath = relative(nuxt.options.rootDir, sanityConfigPath);
if (!relativeSanityConfigPath.startsWith("..")) {
nuxt.options.watch.push(createRegExp(exactly(relativeSanityConfigPath)));
}
const jiti = createJiti(import.meta.url, { jsx: true });
if (existsSync(sanityConfigPath)) {
const sanityConfig = await jiti.import(sanityConfigPath, { default: true, try: true });
if (sanityConfig) {
options.projectId ||= sanityConfig.projectId;
options.dataset ||= sanityConfig.dataset;
}
}
}
options.dataset ||= "production";
if (options.liveContent && options.minimal) {
throw new Error("Live Content API is not supported by the minimal client.");
}
if (options.visualEditing) {
try {
if (options.minimal) {
throw new Error("Minimal client is enabled.");
}
if (!options.visualEditing.token) {
throw new Error(`'token' is required.`);
}
if (!options.visualEditing.studioUrl) {
throw new Error(`'studioUrl' is required.`);
}
if (options.apiVersion === "1") {
throw new Error(`The specified API Version must be ${colors.bold("2021-03-25")} or later.`);
}
} catch (e) {
options.visualEditing = void 0;
if (e instanceof Error) {
logger.warn(`Could not enable visual editing: ${e.message}`);
}
}
}
const runtimeConfig = {};
const publicRuntimeConfig = {
additionalClients: options.additionalClients || {},
apiVersion: options.apiVersion || "1",
dataset: options.dataset,
disableSmartCdn: options.disableSmartCdn ?? false,
perspective: options.perspective || "raw",
projectId: options.projectId || "",
stega: options.visualEditing && options.visualEditing.stega !== false && {
enabled: true,
studioUrl: options.visualEditing.studioUrl
} || {},
token: options.token || "",
useCdn: options.useCdn ?? true,
withCredentials: options.withCredentials ?? false
};
if (options.visualEditing) {
const previewMode = options.visualEditing.previewMode !== false ? defu(options.visualEditing.previewMode, {
enable: "/preview/enable",
disable: "/preview/disable"
}) : false;
runtimeConfig.visualEditing = {
previewModeId: previewMode ? crypto.randomBytes(16).toString("hex") : "",
token: options.visualEditing.token || ""
};
publicRuntimeConfig.visualEditing = {
mode: options.visualEditing.mode || "live-visual-editing",
previewMode,
previewModeId: "",
proxyEndpoint: options.visualEditing.proxyEndpoint || "/_sanity/fetch",
studioUrl: options.visualEditing.studioUrl || "",
token: "",
zIndex: options.visualEditing.zIndex
};
}
if (options.liveContent) {
runtimeConfig.liveContent = {
serverToken: options.liveContent.serverToken || ""
};
publicRuntimeConfig.liveContent = {
browserToken: options.liveContent.browserToken || "",
serverToken: ""
};
}
nuxt.options.runtimeConfig.sanity = defu(nuxt.options.runtimeConfig.sanity, runtimeConfig);
const { projectId, dataset } = nuxt.options.runtimeConfig.public.sanity = defu(nuxt.options.runtimeConfig.public.sanity, publicRuntimeConfig);
if (!projectId) {
logger.warn(`No Sanity project found. Make sure you specify a ${colors.bold("projectId")} in your Sanity config.`);
} else {
logger.info(`Running with Sanity project ${colors.bold(projectId)} (${colors.bold(dataset)}).`);
}
const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
nuxt.options.build.transpile.push(runtimeDir, "@nuxtjs/sanity");
nuxt.options.build.transpile.push("@sanity/core-loader", "@sanity/preview-url-secret");
const clientSpecifier = options.minimal ? join(runtimeDir, "minimal-client") : "@sanity/client";
addTemplate({
filename: "sanity-client.mjs",
getContents: () => genExport(clientSpecifier, ["createClient"]),
write: true
});
if (options.globalHelper) {
addPlugin({ src: join(runtimeDir, "plugins/global-helper") });
if (isNuxtMajorVersion(2)) {
nuxt.hook("prepare:types", ({ references }) => {
references.push({ types: "@nuxtjs/sanity/dist/runtime/plugins/global-helper" });
});
}
}
const composablesPath = join(runtimeDir, "composables/index");
addImports([
{ name: "useSanity", from: composablesPath },
{ name: "createClient", as: "createSanityClient", from: "#build/sanity-client.mjs" },
{ name: "groq", from: join(runtimeDir, "groq") }
]);
addImports([
...isNuxtMajorVersion(2) ? [] : [{ name: "useSanityQuery", from: composablesPath }],
{ name: "useSanityQuery", from: composablesPath },
{ name: "useLazySanityQuery", from: composablesPath },
{ name: "useSanityConfig", from: composablesPath },
{ name: "useSanityPerspective", from: composablesPath },
...isNuxtMajorVersion(2) ? [] : [{ name: "useSanityVisualEditingState", from: composablesPath }],
{ name: "useIsSanityLivePreview", from: composablesPath },
{ name: "useIsSanityPresentationTool", from: composablesPath },
{ name: "useSanityPreviewPerspective", from: composablesPath },
{ name: "useSanityPreviewEnvironment", from: composablesPath },
// Visual Editing
...isNuxtMajorVersion(2) ? [] : [{ name: "createDataAttribute", from: "@sanity/visual-editing", as: "createSanityDataAttribute" }],
{ name: "sanityVisualEditingRefresh", from: "#build/sanity-visual-editing-refresh.mjs" },
...isNuxtMajorVersion(2) ? [] : [{ name: "useSanityLiveMode", from: composablesPath }],
...isNuxtMajorVersion(2) ? [] : [{ name: "useSanityVisualEditing", from: composablesPath }]
]);
const clientPath = await resolvePath(clientSpecifier);
nuxt.hook("prepare:types", async ({ tsConfig }) => {
tsConfig.compilerOptions ||= {};
tsConfig.compilerOptions.paths["#sanity-client"] = [clientPath];
tsConfig.compilerOptions.paths["#sanity-composables"] = [composablesPath];
});
nuxt.hook("nitro:config", (config) => {
config.typescript = defu(config.typescript, {
tsConfig: {
compilerOptions: {
paths: {
["#sanity-client"]: [clientPath],
["#sanity-composables"]: [composablesPath]
}
}
}
});
if (config.imports === false) return;
config.virtual ||= {};
config.virtual["#sanity-client"] = genExport(clientSpecifier, ["createClient"]);
config.externals ||= {};
config.externals.inline ||= [];
config.externals.inline.push(runtimeDir);
config.imports = defu(config.imports, {
presets: [
{
from: "#sanity-client",
imports: [{ name: "createClient", as: "createSanityClient" }]
},
{
from: join(runtimeDir, "server/utils/index"),
imports: ["useSanity"]
},
{
from: join(runtimeDir, "groq"),
imports: ["groq"]
}
]
});
});
await addComponentsDir({
path: join(runtimeDir, "components"),
extensions: ["js", "ts", "mjs"]
});
if (options.liveContent) {
addPlugin({
mode: "client",
src: join(runtimeDir, "plugins", "live-content.client")
});
}
if (publicRuntimeConfig.visualEditing) {
nuxt.options.build.transpile.push("async-cache-dedupe");
nuxt.options.vite.resolve = defu(nuxt.options.vite.resolve, {
dedupe: ["@sanity/client"]
});
nuxt.options.vite.optimizeDeps = defu(nuxt.options.vite.optimizeDeps, {
include: [
"@nuxtjs/sanity > @sanity/visual-editing > @sanity/visual-editing > react-is",
"@nuxtjs/sanity > @sanity/visual-editing > @sanity/mutate > lodash/groupBy.js",
"@nuxtjs/sanity > @sanity/visual-editing > react",
"@nuxtjs/sanity > @sanity/visual-editing > react/jsx-runtime",
"@nuxtjs/sanity > @sanity/visual-editing > react-dom",
"@nuxtjs/sanity > @sanity/visual-editing > react-dom/client",
"@nuxtjs/sanity > @sanity/visual-editing > react-compiler-runtime",
"@sanity/client"
]
});
addTemplate({
filename: "sanity-visual-editing-refresh.mjs",
getContents: () => `
export const sanityVisualEditingRefresh = ${options.visualEditing?.refresh?.toString() || "undefined"}
`,
write: true
});
addPlugin({
mode: "server",
src: join(runtimeDir, "plugins", "visual-editing.server")
});
if (publicRuntimeConfig.visualEditing.mode !== "custom") {
addPlugin({
mode: "client",
src: join(runtimeDir, "plugins", "visual-editing.client")
});
logger.info(`Visual editing enabled globally.`);
} else {
logger.info(`Call ${colors.bold("useSanityVisualEditing()")} in your application to enable visual editing.`);
}
addServerHandler({
method: "post",
route: publicRuntimeConfig.visualEditing.proxyEndpoint,
handler: join(runtimeDir, "server/routes/preview/proxy")
});
if (publicRuntimeConfig.visualEditing.previewMode !== false) {
addServerHandler({
method: "get",
route: publicRuntimeConfig.visualEditing.previewMode.enable,
handler: join(runtimeDir, "server/routes/preview/enable")
});
addServerHandler({
method: "get",
route: publicRuntimeConfig.visualEditing.previewMode.disable,
handler: join(runtimeDir, "server/routes/preview/disable")
});
logger.info(
`Preview mode enabled. Added routes at: ${Object.values(publicRuntimeConfig.visualEditing.previewMode).map((route) => colors.bold(route)).join(", ")}.`
);
}
}
}
});
export { module as default };