@sigx/ssg 0.2.0 → 0.2.2
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/build-DP9zez3B.js +254 -0
- package/dist/{build-qmtK32gt.js.map → build-DP9zez3B.js.map} +1 -1
- package/dist/build.js +2 -2
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +2 -2
- package/dist/client.js +21 -38
- package/dist/client.js.map +1 -1
- package/dist/dev.js +38 -58
- package/dist/dev.js.map +1 -1
- package/dist/index.js +17 -53
- package/dist/index.js.map +1 -1
- package/dist/plugin-EIAzPLvE.js +446 -0
- package/dist/{plugin-Bik0HMne.js.map → plugin-EIAzPLvE.js.map} +1 -1
- package/dist/virtual-entries-CH-0HuqJ.js +904 -0
- package/dist/{virtual-entries-TuNN2It1.js.map → virtual-entries-CH-0HuqJ.js.map} +1 -1
- package/dist/vite/plugin.js +2 -2
- package/dist/vite/virtual-entries.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/build-qmtK32gt.js +0 -385
- package/dist/plugin-Bik0HMne.js +0 -632
- package/dist/virtual-entries-TuNN2It1.js +0 -1298
package/dist/plugin-Bik0HMne.js
DELETED
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
import { D as parseFrontmatter, E as extractTitleFromContent, O as defineSSGConfig, T as scanPages, _ as generateNavigationModule, b as generateLazyRoutesModule, c as generateHtmlTemplate, d as RESOLVED_VIRTUAL_LAYOUTS_ID, h as RESOLVED_VIRTUAL_NAVIGATION_ID, k as loadConfig, m as discoverLayouts, n as RESOLVED_VIRTUAL_SERVER_ID, o as detectCustomEntries, p as generateLayoutsModule, s as generateClientEntry, t as RESOLVED_VIRTUAL_CLIENT_ID, u as generateServerEntry, v as RESOLVED_VIRTUAL_ROUTES_ID, x as generateRoutesModule } from "./virtual-entries-TuNN2It1.js";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fsSync from "node:fs";
|
|
4
|
-
import { createHighlighter } from "shiki";
|
|
5
|
-
import { visit } from "unist-util-visit";
|
|
6
|
-
import { toString } from "hast-util-to-string";
|
|
7
|
-
//#region src/mdx/shiki.ts
|
|
8
|
-
/**
|
|
9
|
-
* Shiki syntax highlighting integration
|
|
10
|
-
*
|
|
11
|
-
* Provides code block highlighting for Markdown/MDX content.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Cached highlighter instance
|
|
15
|
-
*/
|
|
16
|
-
var highlighterPromise = null;
|
|
17
|
-
/**
|
|
18
|
-
* Default Shiki configuration
|
|
19
|
-
*/
|
|
20
|
-
var DEFAULT_CONFIG = {
|
|
21
|
-
light: "github-light",
|
|
22
|
-
dark: "github-dark",
|
|
23
|
-
langs: [
|
|
24
|
-
"javascript",
|
|
25
|
-
"typescript",
|
|
26
|
-
"jsx",
|
|
27
|
-
"tsx",
|
|
28
|
-
"json",
|
|
29
|
-
"css",
|
|
30
|
-
"html",
|
|
31
|
-
"markdown",
|
|
32
|
-
"bash",
|
|
33
|
-
"shell"
|
|
34
|
-
]
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Initialize or get the Shiki highlighter
|
|
38
|
-
*/
|
|
39
|
-
async function getHighlighter(config) {
|
|
40
|
-
if (!highlighterPromise) {
|
|
41
|
-
const mergedConfig = {
|
|
42
|
-
...DEFAULT_CONFIG,
|
|
43
|
-
...config
|
|
44
|
-
};
|
|
45
|
-
highlighterPromise = createHighlighter({
|
|
46
|
-
themes: [mergedConfig.light, mergedConfig.dark],
|
|
47
|
-
langs: mergedConfig.langs
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return highlighterPromise;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Highlight code with Shiki
|
|
54
|
-
*/
|
|
55
|
-
async function highlightCode(code, lang, config, meta) {
|
|
56
|
-
const highlighter = await getHighlighter(config);
|
|
57
|
-
const mergedConfig = {
|
|
58
|
-
...DEFAULT_CONFIG,
|
|
59
|
-
...config
|
|
60
|
-
};
|
|
61
|
-
const effectiveLang = highlighter.getLoadedLanguages().includes(lang) ? lang : "text";
|
|
62
|
-
const codeHtml = highlighter.codeToHtml(code, {
|
|
63
|
-
lang: effectiveLang,
|
|
64
|
-
themes: {
|
|
65
|
-
light: mergedConfig.light,
|
|
66
|
-
dark: mergedConfig.dark
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
const filename = meta?.filename ?? "";
|
|
70
|
-
const isLive = meta?.live ?? false;
|
|
71
|
-
const tabs = meta?.tabs;
|
|
72
|
-
const hasTabs = tabs && tabs.length > 0;
|
|
73
|
-
const filenameHtml = filename ? `<span class="code-window-filename">${escapeHtml(filename)}</span>` : `<span class="code-window-lang">${getLanguageLabel(effectiveLang)}</span>`;
|
|
74
|
-
if (hasTabs) {
|
|
75
|
-
const base64Code = encodeBase64(code);
|
|
76
|
-
const firstTab = tabs[0];
|
|
77
|
-
const tabButtonsHtml = tabs.map((tab, i) => {
|
|
78
|
-
const label = tab.charAt(0).toUpperCase() + tab.slice(1);
|
|
79
|
-
return `<button class="code-window-tab${i === 0 ? " code-window-tab-active" : ""}">${label}</button>`;
|
|
80
|
-
}).join("\n ");
|
|
81
|
-
return `<div
|
|
82
|
-
class="live-preview-island"
|
|
83
|
-
data-island="LivePreview"
|
|
84
|
-
data-island-strategy="visible"
|
|
85
|
-
data-island-props="${escapeHtml(JSON.stringify({
|
|
86
|
-
code: base64Code,
|
|
87
|
-
highlightedCode: codeHtml,
|
|
88
|
-
language: effectiveLang,
|
|
89
|
-
filename,
|
|
90
|
-
tabs,
|
|
91
|
-
live: isLive
|
|
92
|
-
}))}"
|
|
93
|
-
>
|
|
94
|
-
<div class="code-window code-window-live code-window-preview">
|
|
95
|
-
<div class="code-window-header">
|
|
96
|
-
<div class="code-window-header-left">
|
|
97
|
-
<div class="code-window-dots">
|
|
98
|
-
<span class="code-window-dot dot-red"></span>
|
|
99
|
-
<span class="code-window-dot dot-yellow"></span>
|
|
100
|
-
<span class="code-window-dot dot-green"></span>
|
|
101
|
-
</div>
|
|
102
|
-
${filenameHtml}
|
|
103
|
-
</div>
|
|
104
|
-
<div class="code-window-tabs">
|
|
105
|
-
${tabButtonsHtml}
|
|
106
|
-
</div>
|
|
107
|
-
<button class="code-window-try-live" disabled>⚡ Try Live</button>
|
|
108
|
-
</div>
|
|
109
|
-
<div class="code-window-preview-pane"${firstTab !== "preview" ? " style=\"display:none;\"" : ""}>
|
|
110
|
-
<div class="code-window-preview-loading">
|
|
111
|
-
<span class="code-window-spinner"></span>
|
|
112
|
-
Loading preview...
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
<div class="code-window-console-pane"${firstTab !== "console" ? " style=\"display:none;\"" : ""}>
|
|
116
|
-
<div class="code-window-console-empty">No console output</div>
|
|
117
|
-
</div>
|
|
118
|
-
<div class="code-window-content"${firstTab !== "code" ? " style=\"display:none;\"" : ""}>
|
|
119
|
-
${codeHtml}
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</div>`;
|
|
123
|
-
}
|
|
124
|
-
const tryLiveButton = isLive ? `<button class="code-window-try-live" data-live-code="${escapeHtml(encodeBase64(code))}" data-lang="${effectiveLang}" data-filename="${escapeHtml(filename)}" title="Open in Live Playground">⚡ Try Live</button>` : "";
|
|
125
|
-
return `<div class="code-window${isLive ? " code-window-live" : ""}">
|
|
126
|
-
<div class="code-window-header">
|
|
127
|
-
<div class="code-window-header-left">
|
|
128
|
-
<div class="code-window-dots">
|
|
129
|
-
<span class="code-window-dot dot-red"></span>
|
|
130
|
-
<span class="code-window-dot dot-yellow"></span>
|
|
131
|
-
<span class="code-window-dot dot-green"></span>
|
|
132
|
-
</div>
|
|
133
|
-
${filenameHtml}
|
|
134
|
-
</div>
|
|
135
|
-
${tryLiveButton}
|
|
136
|
-
</div>
|
|
137
|
-
<div class="code-window-content">
|
|
138
|
-
${codeHtml}
|
|
139
|
-
</div>
|
|
140
|
-
</div>`;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Encode string to base64 (works in both Node.js and browser)
|
|
144
|
-
*/
|
|
145
|
-
function encodeBase64(str) {
|
|
146
|
-
if (typeof Buffer !== "undefined") return Buffer.from(str, "utf-8").toString("base64");
|
|
147
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Get a display label for a language
|
|
151
|
-
*/
|
|
152
|
-
function getLanguageLabel(lang) {
|
|
153
|
-
return {
|
|
154
|
-
"tsx": "TSX",
|
|
155
|
-
"jsx": "JSX",
|
|
156
|
-
"ts": "TypeScript",
|
|
157
|
-
"typescript": "TypeScript",
|
|
158
|
-
"js": "JavaScript",
|
|
159
|
-
"javascript": "JavaScript",
|
|
160
|
-
"css": "CSS",
|
|
161
|
-
"html": "HTML",
|
|
162
|
-
"json": "JSON",
|
|
163
|
-
"bash": "Terminal",
|
|
164
|
-
"shell": "Terminal",
|
|
165
|
-
"sh": "Terminal",
|
|
166
|
-
"md": "Markdown",
|
|
167
|
-
"markdown": "Markdown",
|
|
168
|
-
"python": "Python",
|
|
169
|
-
"py": "Python",
|
|
170
|
-
"rust": "Rust",
|
|
171
|
-
"go": "Go",
|
|
172
|
-
"text": ""
|
|
173
|
-
}[lang.toLowerCase()] ?? lang.toUpperCase();
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Escape HTML special characters
|
|
177
|
-
*/
|
|
178
|
-
function escapeHtml(str) {
|
|
179
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Create a rehype plugin for Shiki
|
|
183
|
-
*/
|
|
184
|
-
function rehypeShiki(config) {
|
|
185
|
-
return async (tree) => {
|
|
186
|
-
const { visit } = await import("unist-util-visit");
|
|
187
|
-
const { fromHtml } = await import("hast-util-from-html");
|
|
188
|
-
const nodesToProcess = [];
|
|
189
|
-
visit(tree, "element", (node, index, parent) => {
|
|
190
|
-
if (node.tagName === "pre" && node.children?.[0]?.tagName === "code") nodesToProcess.push({
|
|
191
|
-
node,
|
|
192
|
-
parent,
|
|
193
|
-
index: index ?? 0
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
await Promise.all(nodesToProcess.map(async ({ node, parent, index }) => {
|
|
197
|
-
const codeNode = node.children[0];
|
|
198
|
-
const lang = (codeNode.properties?.className?.[0] || "").replace(/^language-/, "") || "text";
|
|
199
|
-
const metaString = codeNode.data?.meta || codeNode.properties?.metastring || "";
|
|
200
|
-
const filename = extractMeta(metaString, "filename") || extractMeta(metaString, "title") || "";
|
|
201
|
-
const isLive = /\blive\b/i.test(metaString);
|
|
202
|
-
const tabKeywords = [
|
|
203
|
-
"preview",
|
|
204
|
-
"code",
|
|
205
|
-
"console"
|
|
206
|
-
];
|
|
207
|
-
const tabs = [];
|
|
208
|
-
const tabPositions = [];
|
|
209
|
-
for (const keyword of tabKeywords) {
|
|
210
|
-
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
211
|
-
const match = metaString.match(regex);
|
|
212
|
-
if (match && match.index !== void 0) tabPositions.push({
|
|
213
|
-
tab: keyword,
|
|
214
|
-
index: match.index
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
tabPositions.sort((a, b) => a.index - b.index);
|
|
218
|
-
for (const { tab } of tabPositions) tabs.push(tab);
|
|
219
|
-
const fragment = fromHtml(await highlightCode(getTextContent(codeNode).trim(), lang, config, {
|
|
220
|
-
filename,
|
|
221
|
-
live: isLive,
|
|
222
|
-
tabs: tabs.length > 0 ? tabs : void 0
|
|
223
|
-
}), { fragment: true });
|
|
224
|
-
if (parent && typeof index === "number" && fragment.children.length > 0) parent.children[index] = fragment.children[0];
|
|
225
|
-
}));
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Extract a meta value from a meta string
|
|
230
|
-
* Supports: filename="value" or filename='value' or filename=value
|
|
231
|
-
*/
|
|
232
|
-
function extractMeta(metaString, key) {
|
|
233
|
-
if (!metaString) return null;
|
|
234
|
-
const regex = new RegExp(`${key}=["']?([^"'\\s]+)["']?`, "i");
|
|
235
|
-
const match = metaString.match(regex);
|
|
236
|
-
return match ? match[1] : null;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Extract text content from an AST node
|
|
240
|
-
*/
|
|
241
|
-
function getTextContent(node) {
|
|
242
|
-
if (node.type === "text") return node.value;
|
|
243
|
-
if (node.children) return node.children.map(getTextContent).join("");
|
|
244
|
-
return "";
|
|
245
|
-
}
|
|
246
|
-
//#endregion
|
|
247
|
-
//#region src/mdx/rehype-headings.ts
|
|
248
|
-
/**
|
|
249
|
-
* Rehype plugin to extract headings from MDX/MD content
|
|
250
|
-
*
|
|
251
|
-
* Extracts headings (h2-h6 by default) with their IDs and text content
|
|
252
|
-
* for use in table of contents generation.
|
|
253
|
-
*/
|
|
254
|
-
/**
|
|
255
|
-
* Rehype plugin to extract headings from the document
|
|
256
|
-
*
|
|
257
|
-
* Stores extracted headings in `file.data.headings` for later use.
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* ```ts
|
|
261
|
-
* import { rehypeExtractHeadings } from './rehype-headings';
|
|
262
|
-
*
|
|
263
|
-
* // Use with unified
|
|
264
|
-
* unified()
|
|
265
|
-
* .use(rehypeSlug) // First add IDs to headings
|
|
266
|
-
* .use(rehypeExtractHeadings, { minLevel: 2, maxLevel: 3 })
|
|
267
|
-
* ```
|
|
268
|
-
*/
|
|
269
|
-
function rehypeExtractHeadings(options = {}) {
|
|
270
|
-
const { minLevel = 2, maxLevel = 3 } = options;
|
|
271
|
-
return (tree, file) => {
|
|
272
|
-
const headings = [];
|
|
273
|
-
visit(tree, "element", (node) => {
|
|
274
|
-
const match = /^h([1-6])$/.exec(node.tagName);
|
|
275
|
-
if (!match) return;
|
|
276
|
-
const level = parseInt(match[1], 10);
|
|
277
|
-
if (level < minLevel || level > maxLevel) return;
|
|
278
|
-
const id = node.properties?.id;
|
|
279
|
-
if (!id) return;
|
|
280
|
-
const text = toString(node).trim();
|
|
281
|
-
if (!text) return;
|
|
282
|
-
headings.push({
|
|
283
|
-
id,
|
|
284
|
-
text,
|
|
285
|
-
level
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
file.data = file.data || {};
|
|
289
|
-
file.data.headings = headings;
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/mdx/plugin.ts
|
|
294
|
-
/**
|
|
295
|
-
* Create the MDX Vite plugin
|
|
296
|
-
*/
|
|
297
|
-
function mdxPlugin(options = {}) {
|
|
298
|
-
const { markdown = {} } = options;
|
|
299
|
-
let mdxRollup;
|
|
300
|
-
let viteConfig;
|
|
301
|
-
return {
|
|
302
|
-
name: "sigx-ssg-mdx",
|
|
303
|
-
enforce: "pre",
|
|
304
|
-
async configResolved(config) {
|
|
305
|
-
viteConfig = config;
|
|
306
|
-
const mdxModule = await import("@mdx-js/rollup");
|
|
307
|
-
const remarkFrontmatter = (await import("remark-frontmatter")).default;
|
|
308
|
-
const remarkMdxFrontmatter = (await import("remark-mdx-frontmatter")).default;
|
|
309
|
-
const remarkGfm = (await import("remark-gfm")).default;
|
|
310
|
-
const rehypeSlug = (await import("rehype-slug")).default;
|
|
311
|
-
const rehypeAutolinkHeadings = (await import("rehype-autolink-headings")).default;
|
|
312
|
-
const tocConfig = options.ssgConfig?.toc || {
|
|
313
|
-
minLevel: 2,
|
|
314
|
-
maxLevel: 3
|
|
315
|
-
};
|
|
316
|
-
const rehypePlugins = [];
|
|
317
|
-
rehypePlugins.push(rehypeSlug);
|
|
318
|
-
rehypePlugins.push([rehypeAutolinkHeadings, {
|
|
319
|
-
behavior: "append",
|
|
320
|
-
properties: {
|
|
321
|
-
class: "heading-anchor",
|
|
322
|
-
ariaHidden: true,
|
|
323
|
-
tabIndex: -1
|
|
324
|
-
},
|
|
325
|
-
content: {
|
|
326
|
-
type: "element",
|
|
327
|
-
tagName: "span",
|
|
328
|
-
properties: { class: "heading-anchor-icon" },
|
|
329
|
-
children: [{
|
|
330
|
-
type: "text",
|
|
331
|
-
value: "#"
|
|
332
|
-
}]
|
|
333
|
-
}
|
|
334
|
-
}]);
|
|
335
|
-
rehypePlugins.push([rehypeExtractHeadings, tocConfig]);
|
|
336
|
-
if (markdown.shiki !== false) {
|
|
337
|
-
const shikiConfig = typeof markdown.shiki === "object" ? markdown.shiki : void 0;
|
|
338
|
-
rehypePlugins.push([rehypeShiki, shikiConfig]);
|
|
339
|
-
}
|
|
340
|
-
if (markdown.rehypePlugins) rehypePlugins.push(...markdown.rehypePlugins);
|
|
341
|
-
const remarkPlugins = [
|
|
342
|
-
remarkFrontmatter,
|
|
343
|
-
[remarkMdxFrontmatter, { name: "frontmatter" }],
|
|
344
|
-
remarkGfm
|
|
345
|
-
];
|
|
346
|
-
if (markdown.remarkPlugins) remarkPlugins.push(...markdown.remarkPlugins);
|
|
347
|
-
mdxRollup = mdxModule.default({
|
|
348
|
-
jsx: false,
|
|
349
|
-
jsxImportSource: "sigx",
|
|
350
|
-
remarkPlugins,
|
|
351
|
-
rehypePlugins,
|
|
352
|
-
providerImportSource: void 0
|
|
353
|
-
});
|
|
354
|
-
},
|
|
355
|
-
async transform(code, id) {
|
|
356
|
-
if (!/\.mdx?$/.test(id)) return null;
|
|
357
|
-
const { data: frontmatter, content } = parseFrontmatter(code);
|
|
358
|
-
if (!frontmatter.title) {
|
|
359
|
-
const extractedTitle = extractTitleFromContent(content);
|
|
360
|
-
if (extractedTitle) frontmatter.title = extractedTitle;
|
|
361
|
-
}
|
|
362
|
-
if (!mdxRollup?.transform) throw new Error("MDX plugin not initialized");
|
|
363
|
-
const result = await mdxRollup.transform(code, id);
|
|
364
|
-
if (!result) return null;
|
|
365
|
-
const headings = await extractHeadingsFromContent(content, options);
|
|
366
|
-
const moduleId = id.replace(/\\/g, "/");
|
|
367
|
-
return {
|
|
368
|
-
code: wrapMDXComponent(result.code, frontmatter, headings, moduleId, viteConfig.command === "serve"),
|
|
369
|
-
map: result.map
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Wrap MDX output in a SignalX component
|
|
376
|
-
*
|
|
377
|
-
* Note: remark-mdx-frontmatter already exports `frontmatter`, so we only add the layout export
|
|
378
|
-
*
|
|
379
|
-
* In dev mode, we:
|
|
380
|
-
* 1. Wrap MDXContent in a sigx component() for proper HMR tracking
|
|
381
|
-
* 2. Inject HMR registration code so the component updates live
|
|
382
|
-
*/
|
|
383
|
-
function wrapMDXComponent(code, frontmatter, headings, moduleId, isDev) {
|
|
384
|
-
if (isDev) {
|
|
385
|
-
const fileName = moduleId.split("/").pop()?.replace(/\.mdx?$/, "") || "MDXPage";
|
|
386
|
-
const componentName = fileName.charAt(0).toUpperCase() + fileName.slice(1).replace(/[^a-zA-Z0-9]/g, "") + "Page";
|
|
387
|
-
return `
|
|
388
|
-
import { registerHMRModule } from '@sigx/vite/hmr';
|
|
389
|
-
import { component as __component } from 'sigx';
|
|
390
|
-
registerHMRModule('${moduleId}');
|
|
391
|
-
|
|
392
|
-
${code.replace(/export\s+default\s+function\s+MDXContent/g, "function _MDXContent").replace(/export\s+{\s*MDXContent\s+as\s+default\s*}/g, "")}
|
|
393
|
-
|
|
394
|
-
// Export layout from frontmatter for SSG routing
|
|
395
|
-
export const layout = ${frontmatter.layout ? JSON.stringify(frontmatter.layout) : "undefined"};
|
|
396
|
-
|
|
397
|
-
// Export headings for table of contents
|
|
398
|
-
export const headings = ${JSON.stringify(headings)};
|
|
399
|
-
|
|
400
|
-
// Wrap MDXContent in a sigx component for HMR support
|
|
401
|
-
const MDXPage = __component(() => {
|
|
402
|
-
return () => _MDXContent({});
|
|
403
|
-
}, { name: '${componentName}' });
|
|
404
|
-
|
|
405
|
-
export default MDXPage;
|
|
406
|
-
|
|
407
|
-
if (import.meta.hot) {
|
|
408
|
-
// Accept HMR updates with a callback to trigger re-render after module is updated
|
|
409
|
-
import.meta.hot.accept((newModule) => {
|
|
410
|
-
if (newModule) {
|
|
411
|
-
// Notify LayoutRouter to clear its cache and re-render with new module
|
|
412
|
-
window.dispatchEvent(new CustomEvent('sigx:mdx-hmr', {
|
|
413
|
-
detail: { moduleId: '${moduleId}', newModule }
|
|
414
|
-
}));
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
`;
|
|
419
|
-
}
|
|
420
|
-
return `
|
|
421
|
-
${code}
|
|
422
|
-
|
|
423
|
-
// Export layout from frontmatter for SSG routing
|
|
424
|
-
export const layout = ${frontmatter.layout ? JSON.stringify(frontmatter.layout) : "undefined"};
|
|
425
|
-
|
|
426
|
-
// Export headings for table of contents
|
|
427
|
-
export const headings = ${JSON.stringify(headings)};
|
|
428
|
-
`;
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Extract headings from markdown/MDX content
|
|
432
|
-
*/
|
|
433
|
-
async function extractHeadingsFromContent(content, options) {
|
|
434
|
-
const { unified } = await import("unified");
|
|
435
|
-
const remarkParse = (await import("remark-parse")).default;
|
|
436
|
-
const remarkRehype = (await import("remark-rehype")).default;
|
|
437
|
-
const rehypeSlug = (await import("rehype-slug")).default;
|
|
438
|
-
const rehypeStringify = (await import("rehype-stringify")).default;
|
|
439
|
-
const tocConfig = options.ssgConfig?.toc || {
|
|
440
|
-
minLevel: 2,
|
|
441
|
-
maxLevel: 3
|
|
442
|
-
};
|
|
443
|
-
return (await unified().use(remarkParse).use(remarkRehype).use(rehypeSlug).use(rehypeExtractHeadings, tocConfig).use(rehypeStringify).process(content)).data.headings || [];
|
|
444
|
-
}
|
|
445
|
-
//#endregion
|
|
446
|
-
//#region src/vite/plugin.ts
|
|
447
|
-
/**
|
|
448
|
-
* Virtual module for SSG config
|
|
449
|
-
*/
|
|
450
|
-
var VIRTUAL_CONFIG_ID = "virtual:ssg-config";
|
|
451
|
-
var RESOLVED_VIRTUAL_CONFIG_ID = "\0" + VIRTUAL_CONFIG_ID;
|
|
452
|
-
/**
|
|
453
|
-
* Create the SSG Vite plugin
|
|
454
|
-
*/
|
|
455
|
-
function ssgPlugin(options = {}) {
|
|
456
|
-
let config;
|
|
457
|
-
let ssgConfig;
|
|
458
|
-
let root;
|
|
459
|
-
let entryDetection;
|
|
460
|
-
let routesCache = null;
|
|
461
|
-
let layoutsCache = null;
|
|
462
|
-
let navigationCache = null;
|
|
463
|
-
const frontmatterHashCache = /* @__PURE__ */ new Map();
|
|
464
|
-
const mainPlugin = {
|
|
465
|
-
name: "sigx-ssg",
|
|
466
|
-
enforce: "pre",
|
|
467
|
-
async configResolved(resolvedConfig) {
|
|
468
|
-
config = resolvedConfig;
|
|
469
|
-
root = resolvedConfig.root;
|
|
470
|
-
ssgConfig = defineSSGConfig({
|
|
471
|
-
...await loadConfig(options.configPath),
|
|
472
|
-
...options
|
|
473
|
-
});
|
|
474
|
-
entryDetection = detectCustomEntries(root, ssgConfig);
|
|
475
|
-
if (entryDetection.useVirtualClient || entryDetection.useVirtualServer) {
|
|
476
|
-
console.log("📦 @sigx/ssg: Using zero-config mode");
|
|
477
|
-
if (entryDetection.useVirtualClient) console.log(" → Virtual client entry");
|
|
478
|
-
if (entryDetection.useVirtualServer) console.log(" → Virtual server entry");
|
|
479
|
-
if (entryDetection.globalCssPath) console.log(` → Auto-importing ${entryDetection.globalCssPath}`);
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
configureServer(devServer) {
|
|
483
|
-
const pagesDir = path.resolve(root, ssgConfig.pages || "src/pages");
|
|
484
|
-
const layoutsDir = path.resolve(root, ssgConfig.layouts || "src/layouts");
|
|
485
|
-
path.resolve(root, ssgConfig.content || "src/content");
|
|
486
|
-
devServer.watcher.on("add", (file) => {
|
|
487
|
-
if (file.startsWith(pagesDir)) {
|
|
488
|
-
routesCache = null;
|
|
489
|
-
navigationCache = null;
|
|
490
|
-
invalidateModule(RESOLVED_VIRTUAL_ROUTES_ID);
|
|
491
|
-
invalidateModule(RESOLVED_VIRTUAL_NAVIGATION_ID);
|
|
492
|
-
} else if (file.startsWith(layoutsDir)) {
|
|
493
|
-
layoutsCache = null;
|
|
494
|
-
invalidateModule(RESOLVED_VIRTUAL_LAYOUTS_ID);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
devServer.watcher.on("unlink", (file) => {
|
|
498
|
-
if (file.startsWith(pagesDir)) {
|
|
499
|
-
routesCache = null;
|
|
500
|
-
navigationCache = null;
|
|
501
|
-
frontmatterHashCache.delete(file);
|
|
502
|
-
invalidateModule(RESOLVED_VIRTUAL_ROUTES_ID);
|
|
503
|
-
invalidateModule(RESOLVED_VIRTUAL_NAVIGATION_ID);
|
|
504
|
-
} else if (file.startsWith(layoutsDir)) {
|
|
505
|
-
layoutsCache = null;
|
|
506
|
-
invalidateModule(RESOLVED_VIRTUAL_LAYOUTS_ID);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
devServer.watcher.on("change", async (file) => {
|
|
510
|
-
if (!file.startsWith(pagesDir)) return;
|
|
511
|
-
if (!/\.mdx?$/.test(file)) return;
|
|
512
|
-
try {
|
|
513
|
-
const { data: newFrontmatter } = parseFrontmatter(await fsSync.promises.readFile(file, "utf-8"));
|
|
514
|
-
const newHash = JSON.stringify(newFrontmatter);
|
|
515
|
-
const oldHash = frontmatterHashCache.get(file);
|
|
516
|
-
frontmatterHashCache.set(file, newHash);
|
|
517
|
-
if (oldHash !== void 0 && oldHash !== newHash) {
|
|
518
|
-
navigationCache = null;
|
|
519
|
-
routesCache = null;
|
|
520
|
-
const navMod = devServer.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION_ID);
|
|
521
|
-
if (navMod) devServer.moduleGraph.invalidateModule(navMod);
|
|
522
|
-
const routesMod = devServer.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES_ID);
|
|
523
|
-
if (routesMod) devServer.moduleGraph.invalidateModule(routesMod);
|
|
524
|
-
devServer.ws.send({ type: "full-reload" });
|
|
525
|
-
}
|
|
526
|
-
} catch (err) {}
|
|
527
|
-
});
|
|
528
|
-
function invalidateModule(id) {
|
|
529
|
-
const mod = devServer.moduleGraph.getModuleById(id);
|
|
530
|
-
if (mod) {
|
|
531
|
-
devServer.moduleGraph.invalidateModule(mod);
|
|
532
|
-
devServer.ws.send({ type: "full-reload" });
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (entryDetection.useVirtualHtml) devServer.middlewares.use((req, res, next) => {
|
|
536
|
-
if (req.url?.startsWith("/@") || req.url?.startsWith("/__") || req.url?.includes("virtual:") || req.url?.includes("node_modules") || req.url?.startsWith("/@vite") || req.url?.startsWith("/@fs")) return next();
|
|
537
|
-
if (req.url && (req.url === "/" || !req.url.includes("."))) {
|
|
538
|
-
const html = generateHtmlTemplate(ssgConfig);
|
|
539
|
-
devServer.transformIndexHtml(req.url, html).then((transformedHtml) => {
|
|
540
|
-
res.setHeader("Content-Type", "text/html");
|
|
541
|
-
res.end(transformedHtml);
|
|
542
|
-
}).catch(next);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
next();
|
|
546
|
-
});
|
|
547
|
-
},
|
|
548
|
-
resolveId(id) {
|
|
549
|
-
if (id === "virtual:ssg-routes") return RESOLVED_VIRTUAL_ROUTES_ID;
|
|
550
|
-
if (id === "virtual:generated-layouts") return RESOLVED_VIRTUAL_LAYOUTS_ID;
|
|
551
|
-
if (id === VIRTUAL_CONFIG_ID) return RESOLVED_VIRTUAL_CONFIG_ID;
|
|
552
|
-
if (id === "virtual:ssg-navigation") return RESOLVED_VIRTUAL_NAVIGATION_ID;
|
|
553
|
-
if (id === "virtual:ssg-client" || id === "/@ssg/client.tsx") return RESOLVED_VIRTUAL_CLIENT_ID;
|
|
554
|
-
if (id === "virtual:ssg-server") return RESOLVED_VIRTUAL_SERVER_ID;
|
|
555
|
-
return null;
|
|
556
|
-
},
|
|
557
|
-
async load(id) {
|
|
558
|
-
if (id === "\0virtual:ssg-routes") {
|
|
559
|
-
if (!routesCache) {
|
|
560
|
-
const routes = await scanPages(ssgConfig, root);
|
|
561
|
-
routesCache = {
|
|
562
|
-
routes,
|
|
563
|
-
code: config.command === "serve" ? generateLazyRoutesModule(routes, ssgConfig) : generateRoutesModule(routes, ssgConfig)
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
return routesCache.code;
|
|
567
|
-
}
|
|
568
|
-
if (id === "\0virtual:generated-layouts") {
|
|
569
|
-
if (!layoutsCache) {
|
|
570
|
-
const layouts = await discoverLayouts(ssgConfig, root);
|
|
571
|
-
layoutsCache = {
|
|
572
|
-
layouts,
|
|
573
|
-
code: generateLayoutsModule(layouts, ssgConfig)
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
return layoutsCache.code;
|
|
577
|
-
}
|
|
578
|
-
if (id === "\0virtual:ssg-navigation") {
|
|
579
|
-
if (!navigationCache) {
|
|
580
|
-
if (!routesCache) {
|
|
581
|
-
const routes = await scanPages(ssgConfig, root);
|
|
582
|
-
routesCache = {
|
|
583
|
-
routes,
|
|
584
|
-
code: config.command === "serve" ? generateLazyRoutesModule(routes, ssgConfig) : generateRoutesModule(routes, ssgConfig)
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
const isDev = config.command === "serve";
|
|
588
|
-
navigationCache = { code: generateNavigationModule(routesCache.routes, ssgConfig, isDev) };
|
|
589
|
-
}
|
|
590
|
-
return navigationCache.code;
|
|
591
|
-
}
|
|
592
|
-
if (id === RESOLVED_VIRTUAL_CONFIG_ID) return `export default ${JSON.stringify(ssgConfig)};`;
|
|
593
|
-
if (id === "\0virtual:ssg-client.tsx") {
|
|
594
|
-
const code = generateClientEntry(ssgConfig, entryDetection);
|
|
595
|
-
return (await (await import("esbuild")).transform(code, {
|
|
596
|
-
loader: "tsx",
|
|
597
|
-
jsx: "automatic",
|
|
598
|
-
jsxImportSource: "sigx"
|
|
599
|
-
})).code;
|
|
600
|
-
}
|
|
601
|
-
if (id === "\0virtual:ssg-server.tsx") {
|
|
602
|
-
const code = generateServerEntry(ssgConfig);
|
|
603
|
-
return (await (await import("esbuild")).transform(code, {
|
|
604
|
-
loader: "tsx",
|
|
605
|
-
jsx: "automatic",
|
|
606
|
-
jsxImportSource: "sigx"
|
|
607
|
-
})).code;
|
|
608
|
-
}
|
|
609
|
-
return null;
|
|
610
|
-
},
|
|
611
|
-
async handleHotUpdate({ file, server }) {
|
|
612
|
-
const layoutsDir = path.resolve(root, ssgConfig.layouts || "src/layouts");
|
|
613
|
-
const pagesDir = path.resolve(root, ssgConfig.pages || "src/pages");
|
|
614
|
-
if (file.startsWith(layoutsDir)) {
|
|
615
|
-
layoutsCache = null;
|
|
616
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_LAYOUTS_ID);
|
|
617
|
-
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
618
|
-
return [];
|
|
619
|
-
}
|
|
620
|
-
if (file.startsWith(pagesDir) && /\.mdx?$/.test(file)) return;
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
if (options.enableMdx !== false) return [mainPlugin, mdxPlugin({
|
|
624
|
-
markdown: options.markdown,
|
|
625
|
-
ssgConfig: void 0
|
|
626
|
-
})];
|
|
627
|
-
return [mainPlugin];
|
|
628
|
-
}
|
|
629
|
-
//#endregion
|
|
630
|
-
export { ssgPlugin as t };
|
|
631
|
-
|
|
632
|
-
//# sourceMappingURL=plugin-Bik0HMne.js.map
|