@ox-content/vite-plugin 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +263 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +264 -36
- package/dist/index.js.map +1 -1
- package/dist/tabs.js +1 -1
- package/dist/tabs2.cjs +12 -0
- package/dist/tabs2.cjs.map +1 -1
- package/dist/tabs2.js +7 -1
- package/dist/tabs2.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -6725,39 +6725,40 @@ var import_dist = /* @__PURE__ */ require_chunk.__toESM(require_dist(), 1);
|
|
|
6725
6725
|
/**
|
|
6726
6726
|
* Syntax highlighting with Shiki via rehype.
|
|
6727
6727
|
*/
|
|
6728
|
+
const BUILTIN_LANGS = [
|
|
6729
|
+
"javascript",
|
|
6730
|
+
"typescript",
|
|
6731
|
+
"jsx",
|
|
6732
|
+
"tsx",
|
|
6733
|
+
"vue",
|
|
6734
|
+
"svelte",
|
|
6735
|
+
"html",
|
|
6736
|
+
"css",
|
|
6737
|
+
"scss",
|
|
6738
|
+
"json",
|
|
6739
|
+
"yaml",
|
|
6740
|
+
"markdown",
|
|
6741
|
+
"bash",
|
|
6742
|
+
"shell",
|
|
6743
|
+
"rust",
|
|
6744
|
+
"python",
|
|
6745
|
+
"go",
|
|
6746
|
+
"java",
|
|
6747
|
+
"c",
|
|
6748
|
+
"cpp",
|
|
6749
|
+
"sql",
|
|
6750
|
+
"graphql",
|
|
6751
|
+
"diff",
|
|
6752
|
+
"toml"
|
|
6753
|
+
];
|
|
6728
6754
|
let highlighterPromise = null;
|
|
6729
6755
|
/**
|
|
6730
6756
|
* Get or create the Shiki highlighter.
|
|
6731
6757
|
*/
|
|
6732
|
-
async function getHighlighter(theme) {
|
|
6758
|
+
async function getHighlighter(theme, customLangs = []) {
|
|
6733
6759
|
if (!highlighterPromise) highlighterPromise = (0, shiki.createHighlighter)({
|
|
6734
6760
|
themes: [theme],
|
|
6735
|
-
langs: [
|
|
6736
|
-
"javascript",
|
|
6737
|
-
"typescript",
|
|
6738
|
-
"jsx",
|
|
6739
|
-
"tsx",
|
|
6740
|
-
"vue",
|
|
6741
|
-
"svelte",
|
|
6742
|
-
"html",
|
|
6743
|
-
"css",
|
|
6744
|
-
"scss",
|
|
6745
|
-
"json",
|
|
6746
|
-
"yaml",
|
|
6747
|
-
"markdown",
|
|
6748
|
-
"bash",
|
|
6749
|
-
"shell",
|
|
6750
|
-
"rust",
|
|
6751
|
-
"python",
|
|
6752
|
-
"go",
|
|
6753
|
-
"java",
|
|
6754
|
-
"c",
|
|
6755
|
-
"cpp",
|
|
6756
|
-
"sql",
|
|
6757
|
-
"graphql",
|
|
6758
|
-
"diff",
|
|
6759
|
-
"toml"
|
|
6760
|
-
]
|
|
6761
|
+
langs: [...BUILTIN_LANGS, ...customLangs]
|
|
6761
6762
|
});
|
|
6762
6763
|
return highlighterPromise;
|
|
6763
6764
|
}
|
|
@@ -6765,9 +6766,9 @@ async function getHighlighter(theme) {
|
|
|
6765
6766
|
* Rehype plugin for syntax highlighting with Shiki.
|
|
6766
6767
|
*/
|
|
6767
6768
|
function rehypeShikiHighlight(options) {
|
|
6768
|
-
const { theme } = options;
|
|
6769
|
+
const { theme, langs } = options;
|
|
6769
6770
|
return async (tree) => {
|
|
6770
|
-
const highlighter = await getHighlighter(theme);
|
|
6771
|
+
const highlighter = await getHighlighter(theme, langs);
|
|
6771
6772
|
const visit = async (node) => {
|
|
6772
6773
|
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
6773
6774
|
const child = node.children[i];
|
|
@@ -6810,8 +6811,11 @@ function getTextContent(node) {
|
|
|
6810
6811
|
/**
|
|
6811
6812
|
* Apply syntax highlighting to HTML using Shiki.
|
|
6812
6813
|
*/
|
|
6813
|
-
async function highlightCode(html, theme = "github-dark") {
|
|
6814
|
-
const result = await (0, unified.unified)().use(rehype_parse.default, { fragment: true }).use(rehypeShikiHighlight, {
|
|
6814
|
+
async function highlightCode(html, theme = "github-dark", langs = []) {
|
|
6815
|
+
const result = await (0, unified.unified)().use(rehype_parse.default, { fragment: true }).use(rehypeShikiHighlight, {
|
|
6816
|
+
theme,
|
|
6817
|
+
langs
|
|
6818
|
+
}).use(rehype_stringify.default).process(html);
|
|
6815
6819
|
return String(result);
|
|
6816
6820
|
}
|
|
6817
6821
|
|
|
@@ -6989,7 +6993,7 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
6989
6993
|
if (options.mermaid) html = await require_mermaid.transformMermaidStatic(html);
|
|
6990
6994
|
const { html: protectedHtml, svgs } = protectMermaidSvgs(html);
|
|
6991
6995
|
html = protectedHtml;
|
|
6992
|
-
if (options.highlight) html = await highlightCode(html, options.highlightTheme);
|
|
6996
|
+
if (options.highlight) html = await highlightCode(html, options.highlightTheme, options.highlightLangs);
|
|
6993
6997
|
html = restoreMermaidSvgs(html, svgs);
|
|
6994
6998
|
return {
|
|
6995
6999
|
code: generateModuleCode(html, frontmatter, toc, filePath, options),
|
|
@@ -8340,7 +8344,7 @@ function createVueCompilerPlugin() {
|
|
|
8340
8344
|
filename: id,
|
|
8341
8345
|
id
|
|
8342
8346
|
});
|
|
8343
|
-
if (templateResult.errors.length > 0) throw new Error(`[ox-content:og-image] Vue template compilation errors in ${id}: ${templateResult.errors.join(", ")}`);
|
|
8347
|
+
if (templateResult.errors.length > 0) throw new Error(`[ox-content:og-image] Vue template compilation errors in ${id}: ${templateResult.errors.map(String).join(", ")}`);
|
|
8344
8348
|
scriptCode = `${templateResult.code}\nexport default { render }`;
|
|
8345
8349
|
}
|
|
8346
8350
|
const isTs = !!(descriptor.scriptSetup?.lang === "ts" || descriptor.script?.lang === "ts");
|
|
@@ -8678,6 +8682,12 @@ function getComponentName(el) {
|
|
|
8678
8682
|
}
|
|
8679
8683
|
let islandCounter = 0;
|
|
8680
8684
|
/**
|
|
8685
|
+
* Reset island counter (for testing).
|
|
8686
|
+
*/
|
|
8687
|
+
function resetIslandCounter() {
|
|
8688
|
+
islandCounter = 0;
|
|
8689
|
+
}
|
|
8690
|
+
/**
|
|
8681
8691
|
* Rehype plugin to transform Island components.
|
|
8682
8692
|
*/
|
|
8683
8693
|
function rehypeIslands(collectedIslands) {
|
|
@@ -8771,7 +8781,7 @@ function generateHydrationScript(components) {
|
|
|
8771
8781
|
if (components.length === 0) return "";
|
|
8772
8782
|
return `
|
|
8773
8783
|
import { initIslands } from '@ox-content/islands';
|
|
8774
|
-
${components.map((name
|
|
8784
|
+
${components.map((name) => `import ${name} from './${name}';`).join("\n")}
|
|
8775
8785
|
|
|
8776
8786
|
const components = {
|
|
8777
8787
|
${components.join(",\n ")}
|
|
@@ -10406,6 +10416,207 @@ export default { search, searchOptions, loadIndex };
|
|
|
10406
10416
|
`;
|
|
10407
10417
|
}
|
|
10408
10418
|
|
|
10419
|
+
//#endregion
|
|
10420
|
+
//#region src/dev-server.ts
|
|
10421
|
+
/**
|
|
10422
|
+
* Dev server middleware for ox-content SSG.
|
|
10423
|
+
*
|
|
10424
|
+
* Serves fully-rendered HTML pages (with navigation, theme, etc.)
|
|
10425
|
+
* during `vite dev`, matching the SSG build output.
|
|
10426
|
+
*/
|
|
10427
|
+
/** File extensions to skip in the middleware. */
|
|
10428
|
+
const SKIP_EXTENSIONS = new Set([
|
|
10429
|
+
".js",
|
|
10430
|
+
".ts",
|
|
10431
|
+
".css",
|
|
10432
|
+
".scss",
|
|
10433
|
+
".less",
|
|
10434
|
+
".svg",
|
|
10435
|
+
".png",
|
|
10436
|
+
".jpg",
|
|
10437
|
+
".jpeg",
|
|
10438
|
+
".gif",
|
|
10439
|
+
".webp",
|
|
10440
|
+
".ico",
|
|
10441
|
+
".woff",
|
|
10442
|
+
".woff2",
|
|
10443
|
+
".ttf",
|
|
10444
|
+
".eot",
|
|
10445
|
+
".json",
|
|
10446
|
+
".map",
|
|
10447
|
+
".mp4",
|
|
10448
|
+
".webm",
|
|
10449
|
+
".mp3",
|
|
10450
|
+
".pdf"
|
|
10451
|
+
]);
|
|
10452
|
+
/** Vite internal URL prefixes to skip. */
|
|
10453
|
+
const VITE_INTERNAL_PREFIXES = [
|
|
10454
|
+
"/@vite/",
|
|
10455
|
+
"/@fs/",
|
|
10456
|
+
"/@id/",
|
|
10457
|
+
"/__"
|
|
10458
|
+
];
|
|
10459
|
+
/**
|
|
10460
|
+
* Check if a request URL should be skipped by the dev server middleware.
|
|
10461
|
+
*/
|
|
10462
|
+
function shouldSkip(url) {
|
|
10463
|
+
for (const prefix of VITE_INTERNAL_PREFIXES) if (url.startsWith(prefix)) return true;
|
|
10464
|
+
if (url.includes("/node_modules/")) return true;
|
|
10465
|
+
const extMatch = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
|
|
10466
|
+
if (extMatch) {
|
|
10467
|
+
const ext = "." + extMatch[1].toLowerCase();
|
|
10468
|
+
if (SKIP_EXTENSIONS.has(ext)) return true;
|
|
10469
|
+
}
|
|
10470
|
+
return false;
|
|
10471
|
+
}
|
|
10472
|
+
/**
|
|
10473
|
+
* Resolve a request URL to a markdown file path.
|
|
10474
|
+
* Returns null if no matching file exists.
|
|
10475
|
+
*/
|
|
10476
|
+
async function resolveMarkdownFile(url, srcDir) {
|
|
10477
|
+
let pathname = url.split("?")[0].split("#")[0];
|
|
10478
|
+
if (pathname.endsWith("/index.html")) pathname = pathname.slice(0, -11) || "/";
|
|
10479
|
+
if (pathname !== "/" && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
10480
|
+
let relativePath;
|
|
10481
|
+
if (pathname === "/") relativePath = "index.md";
|
|
10482
|
+
else relativePath = pathname.slice(1) + ".md";
|
|
10483
|
+
const filePath = path$1.join(srcDir, relativePath);
|
|
10484
|
+
try {
|
|
10485
|
+
await fs_promises.access(filePath);
|
|
10486
|
+
return filePath;
|
|
10487
|
+
} catch {
|
|
10488
|
+
const indexPath = path$1.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
|
|
10489
|
+
try {
|
|
10490
|
+
await fs_promises.access(indexPath);
|
|
10491
|
+
return indexPath;
|
|
10492
|
+
} catch {
|
|
10493
|
+
return null;
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
/**
|
|
10498
|
+
* Inject Vite HMR client script into the HTML.
|
|
10499
|
+
*/
|
|
10500
|
+
function injectViteHmrClient(html) {
|
|
10501
|
+
return html.replace("</head>", "<script type=\"module\" src=\"/@vite/client\"><\/script>\n<script type=\"module\">\nif (import.meta.hot) {\n import.meta.hot.on('ox-content:update', () => {\n location.reload();\n });\n}\n<\/script>\n</head>");
|
|
10502
|
+
}
|
|
10503
|
+
/**
|
|
10504
|
+
* Create a dev server cache instance.
|
|
10505
|
+
*/
|
|
10506
|
+
function createDevServerCache() {
|
|
10507
|
+
return {
|
|
10508
|
+
navGroups: null,
|
|
10509
|
+
pages: /* @__PURE__ */ new Map(),
|
|
10510
|
+
siteName: null
|
|
10511
|
+
};
|
|
10512
|
+
}
|
|
10513
|
+
/**
|
|
10514
|
+
* Invalidate navigation cache (called on file add/unlink).
|
|
10515
|
+
*/
|
|
10516
|
+
function invalidateNavCache(cache) {
|
|
10517
|
+
cache.navGroups = null;
|
|
10518
|
+
cache.pages.clear();
|
|
10519
|
+
}
|
|
10520
|
+
/**
|
|
10521
|
+
* Invalidate page cache for a specific file (called on file change).
|
|
10522
|
+
*/
|
|
10523
|
+
function invalidatePageCache(cache, filePath) {
|
|
10524
|
+
cache.pages.delete(filePath);
|
|
10525
|
+
}
|
|
10526
|
+
/**
|
|
10527
|
+
* Resolve site name from options or package.json.
|
|
10528
|
+
*/
|
|
10529
|
+
async function resolveSiteName(options, root) {
|
|
10530
|
+
if (options.ssg.siteName) return options.ssg.siteName;
|
|
10531
|
+
try {
|
|
10532
|
+
const pkgPath = path$1.join(root, "package.json");
|
|
10533
|
+
const pkg = JSON.parse(await fs_promises.readFile(pkgPath, "utf-8"));
|
|
10534
|
+
if (pkg.name) return formatTitle(pkg.name);
|
|
10535
|
+
} catch {}
|
|
10536
|
+
return "Documentation";
|
|
10537
|
+
}
|
|
10538
|
+
/**
|
|
10539
|
+
* Render a single markdown page to full HTML.
|
|
10540
|
+
*/
|
|
10541
|
+
async function renderPage$1(filePath, options, navGroups, siteName, base, root) {
|
|
10542
|
+
const srcDir = path$1.resolve(root, options.srcDir);
|
|
10543
|
+
require_tabs.resetTabGroupCounter();
|
|
10544
|
+
resetIslandCounter();
|
|
10545
|
+
const result = await transformMarkdown(await fs_promises.readFile(filePath, "utf-8"), filePath, options, {
|
|
10546
|
+
convertMdLinks: true,
|
|
10547
|
+
baseUrl: base,
|
|
10548
|
+
sourcePath: filePath
|
|
10549
|
+
});
|
|
10550
|
+
let transformedHtml = result.html;
|
|
10551
|
+
const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
|
|
10552
|
+
transformedHtml = protectedHtml;
|
|
10553
|
+
transformedHtml = await transformAllPlugins(transformedHtml, {
|
|
10554
|
+
tabs: true,
|
|
10555
|
+
youtube: true,
|
|
10556
|
+
github: true,
|
|
10557
|
+
ogp: true,
|
|
10558
|
+
mermaid: true,
|
|
10559
|
+
githubToken: process.env.GITHUB_TOKEN
|
|
10560
|
+
});
|
|
10561
|
+
if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
|
|
10562
|
+
transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
10563
|
+
const title = extractTitle$1(transformedHtml, result.frontmatter);
|
|
10564
|
+
const description = result.frontmatter.description;
|
|
10565
|
+
let entryPage;
|
|
10566
|
+
if (result.frontmatter.layout === "entry") entryPage = {
|
|
10567
|
+
hero: result.frontmatter.hero,
|
|
10568
|
+
features: result.frontmatter.features
|
|
10569
|
+
};
|
|
10570
|
+
let html = await generateHtmlPage({
|
|
10571
|
+
title,
|
|
10572
|
+
description,
|
|
10573
|
+
content: transformedHtml,
|
|
10574
|
+
toc: result.toc,
|
|
10575
|
+
frontmatter: result.frontmatter,
|
|
10576
|
+
path: getUrlPath$1(filePath, srcDir),
|
|
10577
|
+
href: getUrlPath$1(filePath, srcDir) || "/",
|
|
10578
|
+
entryPage
|
|
10579
|
+
}, navGroups, siteName, base, options.ssg.ogImage, options.ssg.theme);
|
|
10580
|
+
html = injectViteHmrClient(html);
|
|
10581
|
+
return html;
|
|
10582
|
+
}
|
|
10583
|
+
/**
|
|
10584
|
+
* Create the dev server middleware for SSG page serving.
|
|
10585
|
+
*/
|
|
10586
|
+
function createDevServerMiddleware(options, root, cache) {
|
|
10587
|
+
const srcDir = path$1.resolve(root, options.srcDir);
|
|
10588
|
+
const base = options.base.endsWith("/") ? options.base : options.base + "/";
|
|
10589
|
+
return async (req, res, next) => {
|
|
10590
|
+
const url = req.url;
|
|
10591
|
+
if (!url) return next();
|
|
10592
|
+
let routeUrl = url;
|
|
10593
|
+
if (base !== "/" && routeUrl.startsWith(base)) routeUrl = "/" + routeUrl.slice(base.length);
|
|
10594
|
+
if (shouldSkip(routeUrl)) return next();
|
|
10595
|
+
const filePath = await resolveMarkdownFile(routeUrl, srcDir);
|
|
10596
|
+
if (!filePath) return next();
|
|
10597
|
+
try {
|
|
10598
|
+
const cached = cache.pages.get(filePath);
|
|
10599
|
+
if (cached) {
|
|
10600
|
+
res.setHeader("Content-Type", "text/html");
|
|
10601
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
10602
|
+
res.end(cached);
|
|
10603
|
+
return;
|
|
10604
|
+
}
|
|
10605
|
+
if (!cache.siteName) cache.siteName = await resolveSiteName(options, root);
|
|
10606
|
+
if (!cache.navGroups) cache.navGroups = buildNavItems(await collectMarkdownFiles$1(srcDir), srcDir, base, ".html");
|
|
10607
|
+
const html = await renderPage$1(filePath, options, cache.navGroups, cache.siteName, base, root);
|
|
10608
|
+
cache.pages.set(filePath, html);
|
|
10609
|
+
res.setHeader("Content-Type", "text/html");
|
|
10610
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
10611
|
+
res.end(html);
|
|
10612
|
+
} catch (err) {
|
|
10613
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10614
|
+
console.error(`[ox-content:dev] Failed to render ${filePath}:`, message);
|
|
10615
|
+
next();
|
|
10616
|
+
}
|
|
10617
|
+
};
|
|
10618
|
+
}
|
|
10619
|
+
|
|
10409
10620
|
//#endregion
|
|
10410
10621
|
//#region src/og-viewer.ts
|
|
10411
10622
|
/**
|
|
@@ -10731,7 +10942,7 @@ function createOgViewerPlugin(options) {
|
|
|
10731
10942
|
res.end(html);
|
|
10732
10943
|
} catch (err) {
|
|
10733
10944
|
res.statusCode = 500;
|
|
10734
|
-
res.end(`OG Viewer error: ${err}`);
|
|
10945
|
+
res.end(`OG Viewer error: ${err instanceof Error ? err.message : String(err)}`);
|
|
10735
10946
|
}
|
|
10736
10947
|
return;
|
|
10737
10948
|
}
|
|
@@ -11405,8 +11616,24 @@ function oxContent(options = {}) {
|
|
|
11405
11616
|
});
|
|
11406
11617
|
}
|
|
11407
11618
|
};
|
|
11619
|
+
const ssgDevCache = createDevServerCache();
|
|
11408
11620
|
const ssgPlugin = {
|
|
11409
11621
|
name: "ox-content:ssg",
|
|
11622
|
+
configureServer(devServer) {
|
|
11623
|
+
if (!resolvedOptions.ssg.enabled) return;
|
|
11624
|
+
const root = config?.root || process.cwd();
|
|
11625
|
+
const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
|
|
11626
|
+
devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
|
|
11627
|
+
devServer.watcher.on("add", (file) => {
|
|
11628
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidateNavCache(ssgDevCache);
|
|
11629
|
+
});
|
|
11630
|
+
devServer.watcher.on("unlink", (file) => {
|
|
11631
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidateNavCache(ssgDevCache);
|
|
11632
|
+
});
|
|
11633
|
+
devServer.watcher.on("change", (file) => {
|
|
11634
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidatePageCache(ssgDevCache, file);
|
|
11635
|
+
});
|
|
11636
|
+
},
|
|
11410
11637
|
async closeBundle() {
|
|
11411
11638
|
if (!resolvedOptions.ssg.enabled) return;
|
|
11412
11639
|
const root = config?.root || process.cwd();
|
|
@@ -11482,6 +11709,7 @@ function resolveOptions(options) {
|
|
|
11482
11709
|
strikethrough: options.strikethrough ?? true,
|
|
11483
11710
|
highlight: options.highlight ?? false,
|
|
11484
11711
|
highlightTheme: options.highlightTheme ?? "github-dark",
|
|
11712
|
+
highlightLangs: options.highlightLangs ?? [],
|
|
11485
11713
|
mermaid: options.mermaid ?? false,
|
|
11486
11714
|
frontmatter: options.frontmatter ?? true,
|
|
11487
11715
|
toc: options.toc ?? true,
|