697 lines
26 KiB
JavaScript
697 lines
26 KiB
JavaScript
import fs from 'fs';
|
|
import { defineNuxtModule, createResolver, addImports, addComponentsDir, addTemplate, addServerPlugin, addPlugin, installModule, extendViteConfig, addVitePlugin } from '@nuxt/kit';
|
|
import { defu } from 'defu';
|
|
import { genSafeVariableName, genImport, genDynamicImport } from 'knitwork';
|
|
import { listen } from 'listhen';
|
|
import { hash } from 'ohash';
|
|
import { resolve, join, relative } from 'pathe';
|
|
import { withLeadingSlash, joinURL, withTrailingSlash } from 'ufo';
|
|
import { createStorage } from 'unstorage';
|
|
import { makeIgnored } from '../dist/runtime/utils/config.js';
|
|
import fsDriver from 'unstorage/drivers/fs';
|
|
import httpDriver from 'unstorage/drivers/http';
|
|
import githubDriver from 'unstorage/drivers/github';
|
|
import { WebSocketServer } from 'ws';
|
|
import { consola } from 'consola';
|
|
|
|
const name = "@nuxt/content";
|
|
const version = "2.13.4";
|
|
|
|
const logger = consola.withTag("@nuxt/content");
|
|
const CACHE_VERSION = 2;
|
|
const MOUNT_PREFIX = "content:source:";
|
|
const PROSE_TAGS = [
|
|
"p",
|
|
"a",
|
|
"blockquote",
|
|
"code-inline",
|
|
"code",
|
|
"em",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"h4",
|
|
"h5",
|
|
"h6",
|
|
"hr",
|
|
"img",
|
|
"ul",
|
|
"ol",
|
|
"li",
|
|
"strong",
|
|
"table",
|
|
"thead",
|
|
"tbody",
|
|
"td",
|
|
"th",
|
|
"tr"
|
|
];
|
|
const unstorageDrivers = {
|
|
fs: fsDriver,
|
|
http: httpDriver,
|
|
github: githubDriver
|
|
};
|
|
async function getMountDriver(mount) {
|
|
const dirverName = mount.driver;
|
|
if (unstorageDrivers[dirverName]) {
|
|
return unstorageDrivers[dirverName](mount);
|
|
}
|
|
try {
|
|
return (await import(mount.driver)).default(mount);
|
|
} catch {
|
|
console.error("Couldn't load driver", mount.driver);
|
|
}
|
|
}
|
|
function useContentMounts(nuxt, storages) {
|
|
const key = (path, prefix = "") => `${MOUNT_PREFIX}${path.replace(/[/:]/g, "_")}${prefix.replace(/\//g, ":")}`;
|
|
const baseDir = nuxt.options.future?.compatibilityVersion === 4 ? nuxt.options.rootDir : nuxt.options.srcDir;
|
|
const storageKeys = Object.keys(storages);
|
|
if (Array.isArray(storages) || // Detect object representation of array `{ '0': 'source1' }`. Nuxt converts this array to object when using `nuxt.config.ts`
|
|
storageKeys.length > 0 && storageKeys.every((i) => i === String(+i))) {
|
|
storages = Object.values(storages);
|
|
logger.warn("Using array syntax to define sources is deprecated. Consider using object syntax.");
|
|
storages = storages.reduce((mounts, storage) => {
|
|
if (typeof storage === "string") {
|
|
mounts[key(storage)] = {
|
|
name: storage,
|
|
driver: "fs",
|
|
prefix: "",
|
|
base: resolve(baseDir, storage)
|
|
};
|
|
}
|
|
if (typeof storage === "object") {
|
|
mounts[key(storage.name, storage.prefix)] = storage;
|
|
}
|
|
return mounts;
|
|
}, {});
|
|
} else {
|
|
storages = Object.entries(storages).reduce((mounts, [name, storage]) => {
|
|
mounts[key(storage.name || name, storage.prefix)] = storage;
|
|
return mounts;
|
|
}, {});
|
|
}
|
|
const defaultStorage = key("content");
|
|
if (!storages[defaultStorage]) {
|
|
storages[defaultStorage] = {
|
|
name: defaultStorage,
|
|
driver: "fs",
|
|
base: resolve(baseDir, "content")
|
|
};
|
|
}
|
|
return storages;
|
|
}
|
|
function createWebSocket() {
|
|
const wss = new WebSocketServer({ noServer: true });
|
|
const serve = (req, socket = req.socket, head = "") => wss.handleUpgrade(req, socket, head, (client) => wss.emit("connection", client, req));
|
|
const broadcast = (data) => {
|
|
data = JSON.stringify(data);
|
|
for (const client of wss.clients) {
|
|
try {
|
|
client.send(data);
|
|
} catch {
|
|
}
|
|
}
|
|
};
|
|
return {
|
|
serve,
|
|
broadcast,
|
|
close: () => {
|
|
wss.clients.forEach((client) => client.close());
|
|
return new Promise((resolve2) => wss.close(resolve2));
|
|
}
|
|
};
|
|
}
|
|
function processMarkdownOptions(options) {
|
|
const anchorLinks = typeof options.anchorLinks === "boolean" ? { depth: options.anchorLinks ? 6 : 0, exclude: [] } : { depth: 4, exclude: [1], ...options.anchorLinks };
|
|
return {
|
|
...options,
|
|
anchorLinks,
|
|
remarkPlugins: resolveMarkdownPlugins(options.remarkPlugins),
|
|
rehypePlugins: resolveMarkdownPlugins(options.rehypePlugins)
|
|
};
|
|
}
|
|
function resolveMarkdownPlugins(plugins) {
|
|
if (Array.isArray(plugins)) {
|
|
return Object.values(plugins).reduce((plugins2, plugin) => {
|
|
const [name, pluginOptions] = Array.isArray(plugin) ? plugin : [plugin, {}];
|
|
plugins2[name] = pluginOptions;
|
|
return plugins2;
|
|
}, {});
|
|
}
|
|
return plugins || {};
|
|
}
|
|
|
|
const module = defineNuxtModule({
|
|
meta: {
|
|
name,
|
|
version,
|
|
configKey: "content",
|
|
compatibility: {
|
|
nuxt: ">=3.0.0-rc.3"
|
|
}
|
|
},
|
|
defaults: {
|
|
// @deprecated
|
|
base: "",
|
|
api: {
|
|
baseURL: "/api/_content"
|
|
},
|
|
watch: {
|
|
ws: {
|
|
port: {
|
|
port: 4e3,
|
|
portRange: [4e3, 4040]
|
|
},
|
|
hostname: "localhost",
|
|
showURL: false
|
|
}
|
|
},
|
|
sources: {},
|
|
ignores: [],
|
|
locales: [],
|
|
defaultLocale: void 0,
|
|
highlight: false,
|
|
markdown: {
|
|
tags: {
|
|
...Object.fromEntries(PROSE_TAGS.map((t) => [t, `prose-${t}`])),
|
|
code: "ProseCodeInline"
|
|
},
|
|
anchorLinks: {
|
|
depth: 4,
|
|
exclude: [1]
|
|
}
|
|
},
|
|
yaml: {},
|
|
csv: {
|
|
delimeter: ",",
|
|
json: true
|
|
},
|
|
navigation: {
|
|
fields: []
|
|
},
|
|
contentHead: true,
|
|
documentDriven: false,
|
|
respectPathCase: false,
|
|
experimental: {
|
|
clientDB: false,
|
|
cacheContents: true,
|
|
stripQueryParameters: false,
|
|
advanceQuery: false,
|
|
search: void 0
|
|
}
|
|
},
|
|
async setup(options, nuxt) {
|
|
const { resolve, resolvePath } = createResolver(import.meta.url);
|
|
const resolveRuntimeModule = (path) => resolve("./runtime", path);
|
|
options.locales = Array.from(new Set([options.defaultLocale, ...options.locales].filter(Boolean)));
|
|
const buildIntegrity = nuxt.options.dev ? void 0 : Date.now();
|
|
if (options.base) {
|
|
logger.warn("content.base is deprecated. Use content.api.baseURL instead.");
|
|
options.api.baseURL = withLeadingSlash(joinURL("api", options.base));
|
|
}
|
|
const contentContext = {
|
|
transformers: [],
|
|
...options
|
|
};
|
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
nitroConfig.prerender = nitroConfig.prerender || {};
|
|
nitroConfig.prerender.routes = nitroConfig.prerender.routes || [];
|
|
nitroConfig.handlers = nitroConfig.handlers || [];
|
|
nitroConfig.handlers.push(
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/query/:qid/**:params`,
|
|
handler: resolveRuntimeModule("./server/api/query")
|
|
},
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/query/:qid`,
|
|
handler: resolveRuntimeModule("./server/api/query")
|
|
},
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/query`,
|
|
handler: resolveRuntimeModule("./server/api/query")
|
|
},
|
|
{
|
|
method: "get",
|
|
route: nuxt.options.dev ? `${options.api.baseURL}/cache.json` : `${options.api.baseURL}/cache.${buildIntegrity}.json`,
|
|
handler: resolveRuntimeModule("./server/api/cache")
|
|
}
|
|
);
|
|
if (options.experimental?.search) {
|
|
const route = nuxt.options.dev ? `${options.api.baseURL}/search` : `${options.api.baseURL}/search-${buildIntegrity}`;
|
|
nitroConfig.handlers.push({
|
|
method: "get",
|
|
route,
|
|
handler: resolveRuntimeModule("./server/api/search")
|
|
});
|
|
nitroConfig.routeRules = nitroConfig.routeRules || {};
|
|
nitroConfig.routeRules[route] = {
|
|
prerender: true,
|
|
// Use text/plain to avoid Nitro render an index.html
|
|
headers: options.experimental.search.indexed ? { "Content-Type": "text/plain; charset=utf-8" } : { "Content-Type": "application/json" }
|
|
};
|
|
}
|
|
if (!nuxt.options.dev) {
|
|
nitroConfig.prerender.routes.unshift(`${options.api.baseURL}/cache.${buildIntegrity}.json`);
|
|
}
|
|
const sources = useContentMounts(nuxt, contentContext.sources);
|
|
nitroConfig.devStorage = Object.assign(nitroConfig.devStorage || {}, sources);
|
|
nitroConfig.devStorage["cache:content"] = {
|
|
driver: "fs",
|
|
base: resolve(nuxt.options.buildDir, "content-cache")
|
|
};
|
|
for (const source of Object.values(sources)) {
|
|
if (source.driver === "fs" && source.base.includes(nuxt.options.srcDir)) {
|
|
const wildcard = join(source.base, "**/*").replace(withTrailingSlash(nuxt.options.srcDir), "");
|
|
nuxt.options.ignore.push(
|
|
// Remove `srcDir` from the path
|
|
wildcard,
|
|
`!${wildcard}.vue`
|
|
);
|
|
}
|
|
}
|
|
nitroConfig.bundledStorage = nitroConfig.bundledStorage || [];
|
|
nitroConfig.bundledStorage.push("/cache/content");
|
|
nitroConfig.externals = defu(typeof nitroConfig.externals === "object" ? nitroConfig.externals : {}, {
|
|
inline: [
|
|
// Inline module runtime in Nitro bundle
|
|
resolve("./runtime")
|
|
]
|
|
});
|
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
nitroConfig.alias["#content/server"] = resolveRuntimeModule(options.experimental.advanceQuery ? "./server" : "./legacy/server");
|
|
const transformers = contentContext.transformers.map((t) => {
|
|
const name2 = genSafeVariableName(relative(nuxt.options.rootDir, t)).replace(/_(45|46|47)/g, "_") + "_" + hash(t);
|
|
return { name: name2, import: genImport(t, name2) };
|
|
});
|
|
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
nitroConfig.virtual["#content/virtual/transformers"] = [
|
|
...transformers.map((t) => t.import),
|
|
`export const transformers = [${transformers.map((t) => t.name).join(", ")}]`,
|
|
'export const getParser = (ext) => transformers.find(p => ext.match(new RegExp(p.extensions.join("|"), "i")) && p.parse)',
|
|
'export const getTransformers = (ext) => transformers.filter(p => ext.match(new RegExp(p.extensions.join("|"), "i")) && p.transform)',
|
|
"export default () => {}"
|
|
].join("\n");
|
|
});
|
|
addImports([
|
|
{ name: "queryContent", as: "queryContent", from: resolveRuntimeModule(`./${options.experimental.advanceQuery ? "" : "legacy/"}composables/query`) },
|
|
{ name: "useContentHelpers", as: "useContentHelpers", from: resolveRuntimeModule("./composables/helpers") },
|
|
{ name: "useContentHead", as: "useContentHead", from: resolveRuntimeModule("./composables/head") },
|
|
{ name: "useContentPreview", as: "useContentPreview", from: resolveRuntimeModule("./composables/preview") },
|
|
{ name: "withContentBase", as: "withContentBase", from: resolveRuntimeModule("./composables/utils") },
|
|
{ name: "useUnwrap", as: "useUnwrap", from: resolveRuntimeModule("./composables/useUnwrap") }
|
|
]);
|
|
if (options.experimental?.search) {
|
|
const defaultSearchOptions = {
|
|
indexed: true,
|
|
ignoredTags: ["script", "style", "pre"],
|
|
filterQuery: { _draft: false, _partial: false },
|
|
options: {
|
|
fields: ["title", "content", "titles"],
|
|
storeFields: ["title", "content", "titles"],
|
|
searchOptions: {
|
|
prefix: true,
|
|
fuzzy: 0.2,
|
|
boost: {
|
|
title: 4,
|
|
content: 2,
|
|
titles: 1
|
|
}
|
|
}
|
|
}
|
|
};
|
|
options.experimental.search = {
|
|
...defaultSearchOptions,
|
|
...options.experimental.search
|
|
};
|
|
nuxt.options.modules.push("@vueuse/nuxt");
|
|
addImports([
|
|
{
|
|
name: "defineMiniSearchOptions",
|
|
as: "defineMiniSearchOptions",
|
|
from: resolveRuntimeModule("./composables/search")
|
|
},
|
|
{
|
|
name: "searchContent",
|
|
as: "searchContent",
|
|
from: resolveRuntimeModule("./composables/search")
|
|
}
|
|
]);
|
|
}
|
|
addComponentsDir({
|
|
path: resolve("./runtime/components"),
|
|
pathPrefix: false,
|
|
prefix: "",
|
|
global: true
|
|
});
|
|
const componentsContext = { components: [] };
|
|
nuxt.hook("components:extend", (newComponents) => {
|
|
componentsContext.components = newComponents.filter((c) => {
|
|
if (c.pascalName.startsWith("Prose") || c.pascalName === "NuxtLink") {
|
|
return true;
|
|
}
|
|
if (c.filePath.includes("@nuxt/content/dist") || c.filePath.includes("@nuxtjs/mdc/dist") || c.filePath.includes("nuxt/dist/app") || c.filePath.includes("NuxtWelcome")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
});
|
|
addTemplate({
|
|
filename: "content-components.mjs",
|
|
getContents({ options: options2 }) {
|
|
const components = options2.getComponents().filter((c) => !c.island).flatMap((c) => {
|
|
const exp = c.export === "default" ? "c.default || c" : `c['${c.export}']`;
|
|
const isClient = c.mode === "client";
|
|
const definitions = [];
|
|
definitions.push(`export const ${c.pascalName} = ${genDynamicImport(c.filePath)}.then(c => ${isClient ? `createClientOnly(${exp})` : exp})`);
|
|
return definitions;
|
|
});
|
|
return components.join("\n");
|
|
},
|
|
options: { getComponents: () => componentsContext.components }
|
|
});
|
|
const typesPath = addTemplate({
|
|
filename: "types/content.d.ts",
|
|
getContents: () => [
|
|
"declare module '#content/server' {",
|
|
` const serverQueryContent: typeof import('${resolve(options.experimental.advanceQuery ? "./runtime/server" : "./runtime/legacy/types")}').serverQueryContent`,
|
|
` const parseContent: typeof import('${resolve("./runtime/server")}').parseContent`,
|
|
"}"
|
|
].join("\n")
|
|
}).dst;
|
|
nuxt.hook("prepare:types", (options2) => {
|
|
options2.references.push({ path: typesPath });
|
|
});
|
|
const _layers = [...nuxt.options._layers].reverse();
|
|
for (const layer of _layers) {
|
|
const srcDir = layer.config.srcDir;
|
|
const globalComponents = resolve(srcDir, "components/content");
|
|
const dirStat = await fs.promises.stat(globalComponents).catch(() => null);
|
|
if (dirStat && dirStat.isDirectory()) {
|
|
nuxt.hook("components:dirs", (dirs) => {
|
|
dirs.unshift({
|
|
path: globalComponents,
|
|
global: true,
|
|
pathPrefix: false,
|
|
prefix: ""
|
|
});
|
|
});
|
|
}
|
|
}
|
|
if (options.navigation) {
|
|
addImports({ name: "fetchContentNavigation", as: "fetchContentNavigation", from: resolveRuntimeModule(`./${options.experimental.advanceQuery ? "" : "legacy/"}composables/navigation`) });
|
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
nitroConfig.handlers = nitroConfig.handlers || [];
|
|
nitroConfig.handlers.push(
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/navigation/:qid/**:params`,
|
|
handler: resolveRuntimeModule("./server/api/navigation")
|
|
},
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/navigation/:qid`,
|
|
handler: resolveRuntimeModule("./server/api/navigation")
|
|
},
|
|
{
|
|
method: "get",
|
|
route: `${options.api.baseURL}/navigation`,
|
|
handler: resolveRuntimeModule("./server/api/navigation")
|
|
}
|
|
);
|
|
});
|
|
} else {
|
|
addImports({ name: "navigationDisabled", as: "fetchContentNavigation", from: resolveRuntimeModule("./composables/utils") });
|
|
}
|
|
if (nuxt.options.dev) {
|
|
addServerPlugin(resolveRuntimeModule("./server/plugins/refresh-cache"));
|
|
}
|
|
if (options.documentDriven) {
|
|
const defaultDocumentDrivenConfig = {
|
|
page: true,
|
|
navigation: true,
|
|
surround: true,
|
|
globals: {},
|
|
layoutFallbacks: ["theme"],
|
|
injectPage: true
|
|
};
|
|
if (options.documentDriven === true) {
|
|
options.documentDriven = defaultDocumentDrivenConfig;
|
|
} else {
|
|
options.documentDriven = {
|
|
...defaultDocumentDrivenConfig,
|
|
...options.documentDriven
|
|
};
|
|
}
|
|
if (options.navigation) {
|
|
options.navigation.fields.push("layout");
|
|
}
|
|
addImports([
|
|
{ name: "useContentState", as: "useContentState", from: resolveRuntimeModule("./composables/content") },
|
|
{ name: "useContent", as: "useContent", from: resolveRuntimeModule("./composables/content") }
|
|
]);
|
|
addPlugin(resolveRuntimeModule(
|
|
options.experimental.advanceQuery ? "./plugins/documentDriven" : "./legacy/plugins/documentDriven"
|
|
));
|
|
if (options.documentDriven.injectPage) {
|
|
nuxt.options.pages = true;
|
|
nuxt.hook("pages:extend", (pages) => {
|
|
if (!pages.find((page) => page.path === "/:slug(.*)*")) {
|
|
pages.unshift({
|
|
name: "slug",
|
|
path: "/:slug(.*)*",
|
|
file: resolveRuntimeModule("./pages/document-driven.vue"),
|
|
children: []
|
|
});
|
|
}
|
|
});
|
|
nuxt.hook("app:resolve", async (app) => {
|
|
if (app.mainComponent?.includes("@nuxt/ui-templates")) {
|
|
app.mainComponent = resolveRuntimeModule("./app.vue");
|
|
} else {
|
|
const appContent = await fs.promises.readFile(app.mainComponent, { encoding: "utf-8" });
|
|
if (appContent.includes("<NuxtLayout") || appContent.includes("<nuxt-layout")) {
|
|
logger.warn([
|
|
"Using `<NuxtLayout>` inside `app.vue` will cause unwanted layout shifting in your application.",
|
|
"Consider removing `<NuxtLayout>` from `app.vue` and using it in your pages."
|
|
].join(" "));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
addImports([
|
|
{ name: "useContentDisabled", as: "useContentState", from: resolveRuntimeModule("./composables/utils") },
|
|
{ name: "useContentDisabled", as: "useContent", from: resolveRuntimeModule("./composables/utils") }
|
|
]);
|
|
}
|
|
await nuxt.callHook("content:context", contentContext);
|
|
contentContext.defaultLocale = contentContext.defaultLocale || contentContext.locales[0];
|
|
const cacheIntegrity = hash({
|
|
locales: options.locales,
|
|
options: options.defaultLocale,
|
|
markdown: options.markdown,
|
|
hightlight: options.highlight
|
|
});
|
|
contentContext.markdown = processMarkdownOptions(contentContext.markdown);
|
|
if (options.markdown?.mdc === false) {
|
|
contentContext.markdown.remarkPlugins["remark-mdc"] = void 0;
|
|
}
|
|
const nuxtMDCOptions = {
|
|
remarkPlugins: contentContext.markdown.remarkPlugins,
|
|
rehypePlugins: contentContext.markdown.rehypePlugins,
|
|
highlight: contentContext.highlight,
|
|
components: {
|
|
prose: true,
|
|
map: contentContext.markdown.tags
|
|
},
|
|
headings: {
|
|
anchorLinks: {
|
|
// Reset defaults
|
|
h2: false,
|
|
h3: false,
|
|
h4: false
|
|
}
|
|
}
|
|
};
|
|
if (contentContext.markdown.anchorLinks) {
|
|
for (let i = 0; i < contentContext.markdown.anchorLinks.depth; i++) {
|
|
nuxtMDCOptions.headings.anchorLinks[`h${i + 1}`] = !contentContext.markdown.anchorLinks.exclude.includes(i + 1);
|
|
}
|
|
}
|
|
await installModule("@nuxtjs/mdc", nuxtMDCOptions);
|
|
extendViteConfig((config) => {
|
|
config.optimizeDeps ||= {};
|
|
config.optimizeDeps.include ||= [];
|
|
config.optimizeDeps.include.push("@nuxt/content > slugify");
|
|
config.optimizeDeps.include = config.optimizeDeps.include.map((id) => id.replace(/^@nuxtjs\/mdc > /, "@nuxt/content > @nuxtjs/mdc > "));
|
|
config.plugins?.push({
|
|
name: "content-slot",
|
|
enforce: "pre",
|
|
transform(code) {
|
|
if (code.includes("ContentSlot")) {
|
|
code = code.replace(/<ContentSlot(\s)+([^/>]*)(:use=['"](\$slots.)?([a-zA-Z0-9_-]*)['"])/g, '<MDCSlot$1$2name="$5"');
|
|
code = code.replace(/<\/ContentSlot>/g, "</MDCSlot>");
|
|
code = code.replace(/<ContentSlot/g, "<MDCSlot");
|
|
code = code.replace(/(['"])ContentSlot['"]/g, "$1MDCSlot$1");
|
|
code = code.replace(/ContentSlot\(([^(]*)(:use=['"](\$slots.)?([a-zA-Z0-9_-]*)['"]|use=['"]([a-zA-Z0-9_-]*)['"])([^)]*)/g, 'MDCSlot($1name="$4"$6');
|
|
return {
|
|
code,
|
|
map: { mappings: "" }
|
|
};
|
|
}
|
|
if (code.includes("content-slot")) {
|
|
code = code.replace(/<content-slot(\s)+([^/>]*)(:use=['"](\$slots.)?([a-zA-Z0-9_-]*)['"])/g, '<MDCSlot$1$2name="$5"');
|
|
code = code.replace(/<\/content-slot>/g, "</MDCSlot>");
|
|
code = code.replace(/<content-slot/g, "<MDCSlot");
|
|
code = code.replace(/(['"])content-slot['"]/g, "$1MDCSlot$1");
|
|
code = code.replace(/content-slot\(([^(]*)(:use=['"](\$slots.)?([a-zA-Z0-9_-]*)['"]|use=['"]([a-zA-Z0-9_-]*)['"])([^)]*)/g, 'MDCSlot($1name="$4"$6');
|
|
return {
|
|
code,
|
|
map: { mappings: "" }
|
|
};
|
|
}
|
|
}
|
|
});
|
|
});
|
|
const contentRuntime = defu(nuxt.options.runtimeConfig.public.content, {
|
|
locales: options.locales,
|
|
defaultLocale: contentContext.defaultLocale || void 0,
|
|
integrity: buildIntegrity,
|
|
experimental: {
|
|
stripQueryParameters: options.experimental.stripQueryParameters,
|
|
advanceQuery: options.experimental.advanceQuery === true,
|
|
clientDB: options.experimental.clientDB && nuxt.options.ssr === false
|
|
},
|
|
respectPathCase: options.respectPathCase ?? false,
|
|
api: {
|
|
baseURL: options.api.baseURL
|
|
},
|
|
navigation: contentContext.navigation,
|
|
// Tags will use in markdown renderer for component replacement
|
|
// @deprecated
|
|
tags: contentContext.markdown.tags,
|
|
// @deprecated
|
|
highlight: options.highlight,
|
|
wsUrl: "",
|
|
// Document-driven configuration
|
|
documentDriven: options.documentDriven,
|
|
host: typeof options.documentDriven !== "boolean" ? options.documentDriven?.host ?? "" : "",
|
|
trailingSlash: typeof options.documentDriven !== "boolean" ? options.documentDriven?.trailingSlash ?? false : false,
|
|
search: options.experimental.search,
|
|
contentHead: options.contentHead ?? true,
|
|
// Anchor link generation config
|
|
// @deprecated
|
|
anchorLinks: options.markdown.anchorLinks
|
|
});
|
|
nuxt.options.runtimeConfig.public.content = contentRuntime;
|
|
nuxt.options.runtimeConfig.content = defu(nuxt.options.runtimeConfig.content, {
|
|
cacheVersion: CACHE_VERSION,
|
|
cacheIntegrity,
|
|
...contentContext
|
|
});
|
|
nuxt.hook("tailwindcss:config", async (tailwindConfig) => {
|
|
const contentPath = resolve(nuxt.options.buildDir, "content-cache", "parsed/**/*.{md,yml,yaml,json}");
|
|
tailwindConfig.content = tailwindConfig.content ?? [];
|
|
if (Array.isArray(tailwindConfig.content)) {
|
|
tailwindConfig.content.push(contentPath);
|
|
} else {
|
|
tailwindConfig.content.files = tailwindConfig.content.files ?? [];
|
|
tailwindConfig.content.files.push(contentPath);
|
|
}
|
|
const [tailwindCssPath] = Array.isArray(nuxt.options.tailwindcss?.cssPath) ? nuxt.options.tailwindcss.cssPath : [nuxt.options.tailwindcss?.cssPath];
|
|
let cssPath = tailwindCssPath ? await resolvePath(tailwindCssPath, { extensions: [".css", ".sass", ".scss", ".less", ".styl"] }) : join(nuxt.options.dir.assets, "css/tailwind.css");
|
|
if (!fs.existsSync(cssPath)) {
|
|
cssPath = await resolvePath("tailwindcss/tailwind.css");
|
|
}
|
|
const contentSources = Object.values(useContentMounts(nuxt, contentContext.sources)).map((mount) => mount.driver === "fs" ? mount.base : void 0).filter(Boolean);
|
|
addVitePlugin({
|
|
enforce: "post",
|
|
name: "nuxt:content:tailwindcss",
|
|
handleHotUpdate(ctx) {
|
|
if (!contentSources.some((cs) => ctx.file.startsWith(cs))) {
|
|
return;
|
|
}
|
|
const extraModules = ctx.server.moduleGraph.getModulesByFile(cssPath) || /* @__PURE__ */ new Set();
|
|
const timestamp = +Date.now();
|
|
for (const mod of extraModules) {
|
|
ctx.server.moduleGraph.invalidateModule(mod, void 0, timestamp);
|
|
}
|
|
setTimeout(() => {
|
|
ctx.server.ws.send({
|
|
type: "update",
|
|
updates: Array.from(extraModules).map((mod) => {
|
|
return {
|
|
type: mod.type === "js" ? "js-update" : "css-update",
|
|
path: mod.url,
|
|
acceptedPath: mod.url,
|
|
timestamp
|
|
};
|
|
})
|
|
});
|
|
}, 100);
|
|
}
|
|
});
|
|
});
|
|
const isIgnored = makeIgnored(contentContext.ignores);
|
|
if (!nuxt.options.dev) {
|
|
nuxt.hook("build:before", async () => {
|
|
const storage = createStorage();
|
|
const sources = useContentMounts(nuxt, contentContext.sources);
|
|
sources["cache:content"] = {
|
|
driver: "fs",
|
|
base: resolve(nuxt.options.buildDir, "content-cache")
|
|
};
|
|
for (const [key, source] of Object.entries(sources)) {
|
|
storage.mount(key, await getMountDriver(source));
|
|
}
|
|
let keys = await storage.getKeys("content:source");
|
|
const invalidKeyCharacters = `'"?#/`.split("");
|
|
keys = keys.filter((key) => {
|
|
if (key.startsWith("preview:") || isIgnored(key)) {
|
|
return false;
|
|
}
|
|
if (invalidKeyCharacters.some((ik) => key.includes(ik))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
await Promise.all(
|
|
keys.map(async (key) => await storage.setItem(
|
|
`cache:content:parsed:${key.substring(15)}`,
|
|
await storage.getItem(key)
|
|
))
|
|
);
|
|
});
|
|
return;
|
|
}
|
|
addPlugin(resolveRuntimeModule("./plugins/ws"));
|
|
nuxt.hook("nitro:init", async (nitro) => {
|
|
if (!options.watch || !options.watch.ws) {
|
|
return;
|
|
}
|
|
const ws = createWebSocket();
|
|
const { server, url } = await listen(() => "Nuxt Content", options.watch.ws);
|
|
nitro.hooks.hook("close", async () => {
|
|
await ws.close();
|
|
await server.close();
|
|
});
|
|
server.on("upgrade", ws.serve);
|
|
nitro.options.runtimeConfig.public.content.wsUrl = url.replace("http", "ws");
|
|
await nitro.storage.removeItem("cache:content:content-index.json");
|
|
await nitro.storage.watch(async (event, key) => {
|
|
if (!key.startsWith(MOUNT_PREFIX) || isIgnored(key)) {
|
|
return;
|
|
}
|
|
key = key.substring(MOUNT_PREFIX.length);
|
|
await nitro.storage.removeItem("cache:content:content-index.json");
|
|
ws.broadcast({ event, key });
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
export { module as default };
|