@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as transformMermaidStatic, t as mermaidClientScript } from "./mermaid2.js";
|
|
2
|
-
import { n as transformTabs, t as generateTabsCSS } from "./tabs2.js";
|
|
2
|
+
import { n as resetTabGroupCounter, r as transformTabs, t as generateTabsCSS } from "./tabs2.js";
|
|
3
3
|
import { n as transformYouTube, t as extractVideoId } from "./youtube2.js";
|
|
4
4
|
import { i as transformGitHub, n as fetchRepoData, r as prefetchGitHubRepos, t as collectGitHubRepos } from "./github2.js";
|
|
5
5
|
import { i as transformOgp, n as fetchOgpData, r as prefetchOgpData, t as collectOgpUrls } from "./ogp2.js";
|
|
@@ -6764,39 +6764,40 @@ var import_dist = /* @__PURE__ */ __toESM(require_dist(), 1);
|
|
|
6764
6764
|
/**
|
|
6765
6765
|
* Syntax highlighting with Shiki via rehype.
|
|
6766
6766
|
*/
|
|
6767
|
+
const BUILTIN_LANGS = [
|
|
6768
|
+
"javascript",
|
|
6769
|
+
"typescript",
|
|
6770
|
+
"jsx",
|
|
6771
|
+
"tsx",
|
|
6772
|
+
"vue",
|
|
6773
|
+
"svelte",
|
|
6774
|
+
"html",
|
|
6775
|
+
"css",
|
|
6776
|
+
"scss",
|
|
6777
|
+
"json",
|
|
6778
|
+
"yaml",
|
|
6779
|
+
"markdown",
|
|
6780
|
+
"bash",
|
|
6781
|
+
"shell",
|
|
6782
|
+
"rust",
|
|
6783
|
+
"python",
|
|
6784
|
+
"go",
|
|
6785
|
+
"java",
|
|
6786
|
+
"c",
|
|
6787
|
+
"cpp",
|
|
6788
|
+
"sql",
|
|
6789
|
+
"graphql",
|
|
6790
|
+
"diff",
|
|
6791
|
+
"toml"
|
|
6792
|
+
];
|
|
6767
6793
|
let highlighterPromise = null;
|
|
6768
6794
|
/**
|
|
6769
6795
|
* Get or create the Shiki highlighter.
|
|
6770
6796
|
*/
|
|
6771
|
-
async function getHighlighter(theme) {
|
|
6797
|
+
async function getHighlighter(theme, customLangs = []) {
|
|
6772
6798
|
if (!highlighterPromise) highlighterPromise = createHighlighter({
|
|
6773
6799
|
themes: [theme],
|
|
6774
|
-
langs: [
|
|
6775
|
-
"javascript",
|
|
6776
|
-
"typescript",
|
|
6777
|
-
"jsx",
|
|
6778
|
-
"tsx",
|
|
6779
|
-
"vue",
|
|
6780
|
-
"svelte",
|
|
6781
|
-
"html",
|
|
6782
|
-
"css",
|
|
6783
|
-
"scss",
|
|
6784
|
-
"json",
|
|
6785
|
-
"yaml",
|
|
6786
|
-
"markdown",
|
|
6787
|
-
"bash",
|
|
6788
|
-
"shell",
|
|
6789
|
-
"rust",
|
|
6790
|
-
"python",
|
|
6791
|
-
"go",
|
|
6792
|
-
"java",
|
|
6793
|
-
"c",
|
|
6794
|
-
"cpp",
|
|
6795
|
-
"sql",
|
|
6796
|
-
"graphql",
|
|
6797
|
-
"diff",
|
|
6798
|
-
"toml"
|
|
6799
|
-
]
|
|
6800
|
+
langs: [...BUILTIN_LANGS, ...customLangs]
|
|
6800
6801
|
});
|
|
6801
6802
|
return highlighterPromise;
|
|
6802
6803
|
}
|
|
@@ -6804,9 +6805,9 @@ async function getHighlighter(theme) {
|
|
|
6804
6805
|
* Rehype plugin for syntax highlighting with Shiki.
|
|
6805
6806
|
*/
|
|
6806
6807
|
function rehypeShikiHighlight(options) {
|
|
6807
|
-
const { theme } = options;
|
|
6808
|
+
const { theme, langs } = options;
|
|
6808
6809
|
return async (tree) => {
|
|
6809
|
-
const highlighter = await getHighlighter(theme);
|
|
6810
|
+
const highlighter = await getHighlighter(theme, langs);
|
|
6810
6811
|
const visit = async (node) => {
|
|
6811
6812
|
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
6812
6813
|
const child = node.children[i];
|
|
@@ -6849,8 +6850,11 @@ function getTextContent(node) {
|
|
|
6849
6850
|
/**
|
|
6850
6851
|
* Apply syntax highlighting to HTML using Shiki.
|
|
6851
6852
|
*/
|
|
6852
|
-
async function highlightCode(html, theme = "github-dark") {
|
|
6853
|
-
const result = await unified().use(rehypeParse, { fragment: true }).use(rehypeShikiHighlight, {
|
|
6853
|
+
async function highlightCode(html, theme = "github-dark", langs = []) {
|
|
6854
|
+
const result = await unified().use(rehypeParse, { fragment: true }).use(rehypeShikiHighlight, {
|
|
6855
|
+
theme,
|
|
6856
|
+
langs
|
|
6857
|
+
}).use(rehypeStringify).process(html);
|
|
6854
6858
|
return String(result);
|
|
6855
6859
|
}
|
|
6856
6860
|
|
|
@@ -7028,7 +7032,7 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
7028
7032
|
if (options.mermaid) html = await transformMermaidStatic(html);
|
|
7029
7033
|
const { html: protectedHtml, svgs } = protectMermaidSvgs(html);
|
|
7030
7034
|
html = protectedHtml;
|
|
7031
|
-
if (options.highlight) html = await highlightCode(html, options.highlightTheme);
|
|
7035
|
+
if (options.highlight) html = await highlightCode(html, options.highlightTheme, options.highlightLangs);
|
|
7032
7036
|
html = restoreMermaidSvgs(html, svgs);
|
|
7033
7037
|
return {
|
|
7034
7038
|
code: generateModuleCode(html, frontmatter, toc, filePath, options),
|
|
@@ -8379,7 +8383,7 @@ function createVueCompilerPlugin() {
|
|
|
8379
8383
|
filename: id,
|
|
8380
8384
|
id
|
|
8381
8385
|
});
|
|
8382
|
-
if (templateResult.errors.length > 0) throw new Error(`[ox-content:og-image] Vue template compilation errors in ${id}: ${templateResult.errors.join(", ")}`);
|
|
8386
|
+
if (templateResult.errors.length > 0) throw new Error(`[ox-content:og-image] Vue template compilation errors in ${id}: ${templateResult.errors.map(String).join(", ")}`);
|
|
8383
8387
|
scriptCode = `${templateResult.code}\nexport default { render }`;
|
|
8384
8388
|
}
|
|
8385
8389
|
const isTs = !!(descriptor.scriptSetup?.lang === "ts" || descriptor.script?.lang === "ts");
|
|
@@ -8717,6 +8721,12 @@ function getComponentName(el) {
|
|
|
8717
8721
|
}
|
|
8718
8722
|
let islandCounter = 0;
|
|
8719
8723
|
/**
|
|
8724
|
+
* Reset island counter (for testing).
|
|
8725
|
+
*/
|
|
8726
|
+
function resetIslandCounter() {
|
|
8727
|
+
islandCounter = 0;
|
|
8728
|
+
}
|
|
8729
|
+
/**
|
|
8720
8730
|
* Rehype plugin to transform Island components.
|
|
8721
8731
|
*/
|
|
8722
8732
|
function rehypeIslands(collectedIslands) {
|
|
@@ -8810,7 +8820,7 @@ function generateHydrationScript(components) {
|
|
|
8810
8820
|
if (components.length === 0) return "";
|
|
8811
8821
|
return `
|
|
8812
8822
|
import { initIslands } from '@ox-content/islands';
|
|
8813
|
-
${components.map((name
|
|
8823
|
+
${components.map((name) => `import ${name} from './${name}';`).join("\n")}
|
|
8814
8824
|
|
|
8815
8825
|
const components = {
|
|
8816
8826
|
${components.join(",\n ")}
|
|
@@ -10445,6 +10455,207 @@ export default { search, searchOptions, loadIndex };
|
|
|
10445
10455
|
`;
|
|
10446
10456
|
}
|
|
10447
10457
|
|
|
10458
|
+
//#endregion
|
|
10459
|
+
//#region src/dev-server.ts
|
|
10460
|
+
/**
|
|
10461
|
+
* Dev server middleware for ox-content SSG.
|
|
10462
|
+
*
|
|
10463
|
+
* Serves fully-rendered HTML pages (with navigation, theme, etc.)
|
|
10464
|
+
* during `vite dev`, matching the SSG build output.
|
|
10465
|
+
*/
|
|
10466
|
+
/** File extensions to skip in the middleware. */
|
|
10467
|
+
const SKIP_EXTENSIONS = new Set([
|
|
10468
|
+
".js",
|
|
10469
|
+
".ts",
|
|
10470
|
+
".css",
|
|
10471
|
+
".scss",
|
|
10472
|
+
".less",
|
|
10473
|
+
".svg",
|
|
10474
|
+
".png",
|
|
10475
|
+
".jpg",
|
|
10476
|
+
".jpeg",
|
|
10477
|
+
".gif",
|
|
10478
|
+
".webp",
|
|
10479
|
+
".ico",
|
|
10480
|
+
".woff",
|
|
10481
|
+
".woff2",
|
|
10482
|
+
".ttf",
|
|
10483
|
+
".eot",
|
|
10484
|
+
".json",
|
|
10485
|
+
".map",
|
|
10486
|
+
".mp4",
|
|
10487
|
+
".webm",
|
|
10488
|
+
".mp3",
|
|
10489
|
+
".pdf"
|
|
10490
|
+
]);
|
|
10491
|
+
/** Vite internal URL prefixes to skip. */
|
|
10492
|
+
const VITE_INTERNAL_PREFIXES = [
|
|
10493
|
+
"/@vite/",
|
|
10494
|
+
"/@fs/",
|
|
10495
|
+
"/@id/",
|
|
10496
|
+
"/__"
|
|
10497
|
+
];
|
|
10498
|
+
/**
|
|
10499
|
+
* Check if a request URL should be skipped by the dev server middleware.
|
|
10500
|
+
*/
|
|
10501
|
+
function shouldSkip(url) {
|
|
10502
|
+
for (const prefix of VITE_INTERNAL_PREFIXES) if (url.startsWith(prefix)) return true;
|
|
10503
|
+
if (url.includes("/node_modules/")) return true;
|
|
10504
|
+
const extMatch = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
|
|
10505
|
+
if (extMatch) {
|
|
10506
|
+
const ext = "." + extMatch[1].toLowerCase();
|
|
10507
|
+
if (SKIP_EXTENSIONS.has(ext)) return true;
|
|
10508
|
+
}
|
|
10509
|
+
return false;
|
|
10510
|
+
}
|
|
10511
|
+
/**
|
|
10512
|
+
* Resolve a request URL to a markdown file path.
|
|
10513
|
+
* Returns null if no matching file exists.
|
|
10514
|
+
*/
|
|
10515
|
+
async function resolveMarkdownFile(url, srcDir) {
|
|
10516
|
+
let pathname = url.split("?")[0].split("#")[0];
|
|
10517
|
+
if (pathname.endsWith("/index.html")) pathname = pathname.slice(0, -11) || "/";
|
|
10518
|
+
if (pathname !== "/" && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
10519
|
+
let relativePath;
|
|
10520
|
+
if (pathname === "/") relativePath = "index.md";
|
|
10521
|
+
else relativePath = pathname.slice(1) + ".md";
|
|
10522
|
+
const filePath = path.join(srcDir, relativePath);
|
|
10523
|
+
try {
|
|
10524
|
+
await fs.access(filePath);
|
|
10525
|
+
return filePath;
|
|
10526
|
+
} catch {
|
|
10527
|
+
const indexPath = path.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
|
|
10528
|
+
try {
|
|
10529
|
+
await fs.access(indexPath);
|
|
10530
|
+
return indexPath;
|
|
10531
|
+
} catch {
|
|
10532
|
+
return null;
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10535
|
+
}
|
|
10536
|
+
/**
|
|
10537
|
+
* Inject Vite HMR client script into the HTML.
|
|
10538
|
+
*/
|
|
10539
|
+
function injectViteHmrClient(html) {
|
|
10540
|
+
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>");
|
|
10541
|
+
}
|
|
10542
|
+
/**
|
|
10543
|
+
* Create a dev server cache instance.
|
|
10544
|
+
*/
|
|
10545
|
+
function createDevServerCache() {
|
|
10546
|
+
return {
|
|
10547
|
+
navGroups: null,
|
|
10548
|
+
pages: /* @__PURE__ */ new Map(),
|
|
10549
|
+
siteName: null
|
|
10550
|
+
};
|
|
10551
|
+
}
|
|
10552
|
+
/**
|
|
10553
|
+
* Invalidate navigation cache (called on file add/unlink).
|
|
10554
|
+
*/
|
|
10555
|
+
function invalidateNavCache(cache) {
|
|
10556
|
+
cache.navGroups = null;
|
|
10557
|
+
cache.pages.clear();
|
|
10558
|
+
}
|
|
10559
|
+
/**
|
|
10560
|
+
* Invalidate page cache for a specific file (called on file change).
|
|
10561
|
+
*/
|
|
10562
|
+
function invalidatePageCache(cache, filePath) {
|
|
10563
|
+
cache.pages.delete(filePath);
|
|
10564
|
+
}
|
|
10565
|
+
/**
|
|
10566
|
+
* Resolve site name from options or package.json.
|
|
10567
|
+
*/
|
|
10568
|
+
async function resolveSiteName(options, root) {
|
|
10569
|
+
if (options.ssg.siteName) return options.ssg.siteName;
|
|
10570
|
+
try {
|
|
10571
|
+
const pkgPath = path.join(root, "package.json");
|
|
10572
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
10573
|
+
if (pkg.name) return formatTitle(pkg.name);
|
|
10574
|
+
} catch {}
|
|
10575
|
+
return "Documentation";
|
|
10576
|
+
}
|
|
10577
|
+
/**
|
|
10578
|
+
* Render a single markdown page to full HTML.
|
|
10579
|
+
*/
|
|
10580
|
+
async function renderPage$1(filePath, options, navGroups, siteName, base, root) {
|
|
10581
|
+
const srcDir = path.resolve(root, options.srcDir);
|
|
10582
|
+
resetTabGroupCounter();
|
|
10583
|
+
resetIslandCounter();
|
|
10584
|
+
const result = await transformMarkdown(await fs.readFile(filePath, "utf-8"), filePath, options, {
|
|
10585
|
+
convertMdLinks: true,
|
|
10586
|
+
baseUrl: base,
|
|
10587
|
+
sourcePath: filePath
|
|
10588
|
+
});
|
|
10589
|
+
let transformedHtml = result.html;
|
|
10590
|
+
const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
|
|
10591
|
+
transformedHtml = protectedHtml;
|
|
10592
|
+
transformedHtml = await transformAllPlugins(transformedHtml, {
|
|
10593
|
+
tabs: true,
|
|
10594
|
+
youtube: true,
|
|
10595
|
+
github: true,
|
|
10596
|
+
ogp: true,
|
|
10597
|
+
mermaid: true,
|
|
10598
|
+
githubToken: process.env.GITHUB_TOKEN
|
|
10599
|
+
});
|
|
10600
|
+
if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
|
|
10601
|
+
transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
10602
|
+
const title = extractTitle$1(transformedHtml, result.frontmatter);
|
|
10603
|
+
const description = result.frontmatter.description;
|
|
10604
|
+
let entryPage;
|
|
10605
|
+
if (result.frontmatter.layout === "entry") entryPage = {
|
|
10606
|
+
hero: result.frontmatter.hero,
|
|
10607
|
+
features: result.frontmatter.features
|
|
10608
|
+
};
|
|
10609
|
+
let html = await generateHtmlPage({
|
|
10610
|
+
title,
|
|
10611
|
+
description,
|
|
10612
|
+
content: transformedHtml,
|
|
10613
|
+
toc: result.toc,
|
|
10614
|
+
frontmatter: result.frontmatter,
|
|
10615
|
+
path: getUrlPath$1(filePath, srcDir),
|
|
10616
|
+
href: getUrlPath$1(filePath, srcDir) || "/",
|
|
10617
|
+
entryPage
|
|
10618
|
+
}, navGroups, siteName, base, options.ssg.ogImage, options.ssg.theme);
|
|
10619
|
+
html = injectViteHmrClient(html);
|
|
10620
|
+
return html;
|
|
10621
|
+
}
|
|
10622
|
+
/**
|
|
10623
|
+
* Create the dev server middleware for SSG page serving.
|
|
10624
|
+
*/
|
|
10625
|
+
function createDevServerMiddleware(options, root, cache) {
|
|
10626
|
+
const srcDir = path.resolve(root, options.srcDir);
|
|
10627
|
+
const base = options.base.endsWith("/") ? options.base : options.base + "/";
|
|
10628
|
+
return async (req, res, next) => {
|
|
10629
|
+
const url = req.url;
|
|
10630
|
+
if (!url) return next();
|
|
10631
|
+
let routeUrl = url;
|
|
10632
|
+
if (base !== "/" && routeUrl.startsWith(base)) routeUrl = "/" + routeUrl.slice(base.length);
|
|
10633
|
+
if (shouldSkip(routeUrl)) return next();
|
|
10634
|
+
const filePath = await resolveMarkdownFile(routeUrl, srcDir);
|
|
10635
|
+
if (!filePath) return next();
|
|
10636
|
+
try {
|
|
10637
|
+
const cached = cache.pages.get(filePath);
|
|
10638
|
+
if (cached) {
|
|
10639
|
+
res.setHeader("Content-Type", "text/html");
|
|
10640
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
10641
|
+
res.end(cached);
|
|
10642
|
+
return;
|
|
10643
|
+
}
|
|
10644
|
+
if (!cache.siteName) cache.siteName = await resolveSiteName(options, root);
|
|
10645
|
+
if (!cache.navGroups) cache.navGroups = buildNavItems(await collectMarkdownFiles$1(srcDir), srcDir, base, ".html");
|
|
10646
|
+
const html = await renderPage$1(filePath, options, cache.navGroups, cache.siteName, base, root);
|
|
10647
|
+
cache.pages.set(filePath, html);
|
|
10648
|
+
res.setHeader("Content-Type", "text/html");
|
|
10649
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
10650
|
+
res.end(html);
|
|
10651
|
+
} catch (err) {
|
|
10652
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10653
|
+
console.error(`[ox-content:dev] Failed to render ${filePath}:`, message);
|
|
10654
|
+
next();
|
|
10655
|
+
}
|
|
10656
|
+
};
|
|
10657
|
+
}
|
|
10658
|
+
|
|
10448
10659
|
//#endregion
|
|
10449
10660
|
//#region src/og-viewer.ts
|
|
10450
10661
|
/**
|
|
@@ -10770,7 +10981,7 @@ function createOgViewerPlugin(options) {
|
|
|
10770
10981
|
res.end(html);
|
|
10771
10982
|
} catch (err) {
|
|
10772
10983
|
res.statusCode = 500;
|
|
10773
|
-
res.end(`OG Viewer error: ${err}`);
|
|
10984
|
+
res.end(`OG Viewer error: ${err instanceof Error ? err.message : String(err)}`);
|
|
10774
10985
|
}
|
|
10775
10986
|
return;
|
|
10776
10987
|
}
|
|
@@ -11444,8 +11655,24 @@ function oxContent(options = {}) {
|
|
|
11444
11655
|
});
|
|
11445
11656
|
}
|
|
11446
11657
|
};
|
|
11658
|
+
const ssgDevCache = createDevServerCache();
|
|
11447
11659
|
const ssgPlugin = {
|
|
11448
11660
|
name: "ox-content:ssg",
|
|
11661
|
+
configureServer(devServer) {
|
|
11662
|
+
if (!resolvedOptions.ssg.enabled) return;
|
|
11663
|
+
const root = config?.root || process.cwd();
|
|
11664
|
+
const srcDir = path.resolve(root, resolvedOptions.srcDir);
|
|
11665
|
+
devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
|
|
11666
|
+
devServer.watcher.on("add", (file) => {
|
|
11667
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidateNavCache(ssgDevCache);
|
|
11668
|
+
});
|
|
11669
|
+
devServer.watcher.on("unlink", (file) => {
|
|
11670
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidateNavCache(ssgDevCache);
|
|
11671
|
+
});
|
|
11672
|
+
devServer.watcher.on("change", (file) => {
|
|
11673
|
+
if (file.startsWith(srcDir) && file.endsWith(".md")) invalidatePageCache(ssgDevCache, file);
|
|
11674
|
+
});
|
|
11675
|
+
},
|
|
11449
11676
|
async closeBundle() {
|
|
11450
11677
|
if (!resolvedOptions.ssg.enabled) return;
|
|
11451
11678
|
const root = config?.root || process.cwd();
|
|
@@ -11521,6 +11748,7 @@ function resolveOptions(options) {
|
|
|
11521
11748
|
strikethrough: options.strikethrough ?? true,
|
|
11522
11749
|
highlight: options.highlight ?? false,
|
|
11523
11750
|
highlightTheme: options.highlightTheme ?? "github-dark",
|
|
11751
|
+
highlightLangs: options.highlightLangs ?? [],
|
|
11524
11752
|
mermaid: options.mermaid ?? false,
|
|
11525
11753
|
frontmatter: options.frontmatter ?? true,
|
|
11526
11754
|
toc: options.toc ?? true,
|