@ox-content/vite-plugin-solid 2.81.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.mjs ADDED
@@ -0,0 +1,672 @@
1
+ import { oxContent, oxContent as oxContent$1, transformMarkdown } from "@ox-content/vite-plugin";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ //#region src/components.ts
5
+ /**
6
+ * Resolves the `components` option into a name → path map, expanding glob
7
+ * patterns against the Vite project root.
8
+ */
9
+ async function resolveComponentsGlob(componentsOption, root) {
10
+ if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) return componentsOption;
11
+ const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
12
+ const result = {};
13
+ for (const pattern of patterns) for (const file of await globFiles(pattern, root)) {
14
+ const baseName = path.basename(file, path.extname(file));
15
+ const relativePath = "./" + path.relative(root, file).replace(/\\/g, "/");
16
+ result[toPascalCase(baseName)] = relativePath;
17
+ }
18
+ return result;
19
+ }
20
+ async function globFiles(pattern, root) {
21
+ const files = [];
22
+ const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
23
+ if (!hasWildcard(normalized)) {
24
+ const fullPath = path.resolve(root, normalized);
25
+ if (fs.existsSync(fullPath)) files.push(fullPath);
26
+ return files;
27
+ }
28
+ const baseDir = path.resolve(root, staticPrefix(normalized));
29
+ if (!fs.existsSync(baseDir)) return files;
30
+ const segments = normalized.split("/");
31
+ const crossesDirectories = normalized.includes("**") || segments.slice(0, -1).some(hasWildcard);
32
+ const candidates = [];
33
+ if (crossesDirectories) await walkDir(baseDir, candidates);
34
+ else {
35
+ const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
36
+ for (const entry of entries) if (entry.isFile()) candidates.push(path.join(baseDir, entry.name));
37
+ }
38
+ const matcher = globToRegExp(normalized);
39
+ for (const candidate of candidates) if (matcher.test(path.relative(root, candidate).replace(/\\/g, "/"))) files.push(candidate);
40
+ return files;
41
+ }
42
+ /** Whether a pattern (or one segment of it) contains a wildcard `globToRegExp` expands. */
43
+ function hasWildcard(pattern) {
44
+ return pattern.includes("*") || pattern.includes("?");
45
+ }
46
+ /** Leading path segments of a pattern that contain no wildcard. */
47
+ function staticPrefix(pattern) {
48
+ const segments = [];
49
+ for (const segment of pattern.split("/")) {
50
+ if (hasWildcard(segment)) break;
51
+ segments.push(segment);
52
+ }
53
+ return segments.join("/");
54
+ }
55
+ /**
56
+ * Translates a glob into an anchored `RegExp`: `**` crosses directory
57
+ * boundaries, `*` and `?` stay within one segment.
58
+ */
59
+ function globToRegExp(pattern) {
60
+ let source = "";
61
+ let index = 0;
62
+ while (index < pattern.length) {
63
+ const char = pattern[index];
64
+ if (char === "*") {
65
+ if (pattern[index + 1] === "*") {
66
+ index += 2;
67
+ if (pattern[index] === "/") {
68
+ index += 1;
69
+ source += "(?:[^/]+/)*";
70
+ } else source += ".*";
71
+ continue;
72
+ }
73
+ source += "[^/]*";
74
+ } else if (char === "?") source += "[^/]";
75
+ else source += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
76
+ index += 1;
77
+ }
78
+ return new RegExp(`^${source}$`);
79
+ }
80
+ async function walkDir(dir, files) {
81
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
82
+ for (const entry of entries) {
83
+ const fullPath = path.join(dir, entry.name);
84
+ if (entry.isDirectory()) await walkDir(fullPath, files);
85
+ else if (entry.isFile()) files.push(fullPath);
86
+ }
87
+ }
88
+ function toPascalCase(str) {
89
+ return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
90
+ }
91
+ //#endregion
92
+ //#region src/environment.ts
93
+ function createSolidMarkdownEnvironment(mode, options) {
94
+ const isSSR = mode === "ssr";
95
+ return {
96
+ build: {
97
+ outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
98
+ ssr: isSSR,
99
+ rollupOptions: { output: {
100
+ format: "esm",
101
+ entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
102
+ } },
103
+ ...isSSR && {
104
+ target: "node18",
105
+ minify: false
106
+ }
107
+ },
108
+ resolve: { conditions: isSSR ? [
109
+ "solid",
110
+ "node",
111
+ "import"
112
+ ] : [
113
+ "solid",
114
+ "browser",
115
+ "import"
116
+ ] },
117
+ optimizeDeps: {
118
+ include: isSSR ? [] : ["solid-js", "solid-js/web"],
119
+ exclude: ["@ox-content/vite-plugin", "@ox-content/vite-plugin-solid"]
120
+ }
121
+ };
122
+ }
123
+ //#endregion
124
+ //#region src/options.ts
125
+ const DEFAULT_MARKDOWN_EXTENSIONS = [
126
+ ".md",
127
+ ".markdown",
128
+ ".mdx"
129
+ ];
130
+ function resolveSolidOptions(options) {
131
+ return {
132
+ srcDir: options.srcDir ?? "docs",
133
+ outDir: options.outDir ?? "dist",
134
+ base: options.base ?? "/",
135
+ extensions: normalizeMarkdownExtensions(options.extensions),
136
+ gfm: options.gfm ?? true,
137
+ autolinks: options.autolinks ?? options.gfm ?? true,
138
+ frontmatter: options.frontmatter ?? true,
139
+ toc: options.toc ?? true,
140
+ tocMaxDepth: options.tocMaxDepth ?? 3,
141
+ codeAnnotations: resolveCodeAnnotationsOptions(options.codeAnnotations),
142
+ verifySolidPlugin: options.verifySolidPlugin ?? true,
143
+ embeds: resolveBuiltinEmbedOptions(options.embeds)
144
+ };
145
+ }
146
+ function normalizeMarkdownExtensions(extensions) {
147
+ const values = extensions?.length ? extensions : DEFAULT_MARKDOWN_EXTENSIONS;
148
+ return Array.from(new Map(values.map((extension) => {
149
+ const value = extension.startsWith(".") ? extension : `.${extension}`;
150
+ return [value.toLowerCase(), value];
151
+ })).values());
152
+ }
153
+ function isMarkdownFilePath(filePath, extensions) {
154
+ const pathname = filePath.split("?")[0].split("#")[0].toLowerCase();
155
+ return extensions.some((extension) => pathname.endsWith(extension.toLowerCase()));
156
+ }
157
+ function resolveCodeAnnotationsOptions(options) {
158
+ if (!options) return {
159
+ enabled: false,
160
+ metaKey: "annotate"
161
+ };
162
+ if (options === true) return {
163
+ enabled: true,
164
+ metaKey: "annotate"
165
+ };
166
+ return {
167
+ enabled: true,
168
+ metaKey: options.metaKey ?? "annotate"
169
+ };
170
+ }
171
+ function resolveBuiltinEmbedOptions(options) {
172
+ if (options === false) return {
173
+ github: false,
174
+ openGraph: false
175
+ };
176
+ return {
177
+ github: resolveSingleEmbedOptions(options?.github),
178
+ openGraph: resolveSingleEmbedOptions(options?.openGraph)
179
+ };
180
+ }
181
+ function resolveSingleEmbedOptions(options) {
182
+ if (options === false) return false;
183
+ if (options === true || options === void 0) return {};
184
+ return options;
185
+ }
186
+ //#endregion
187
+ //#region src/codegen.ts
188
+ /**
189
+ * Generates the Solid module a Markdown file compiles to.
190
+ *
191
+ * The output is JSX on purpose. Solid has no runtime element factory to target
192
+ * — `vite-plugin-solid` compiles this into DOM or SSR instructions — so unlike
193
+ * the React and Vue integrations there is no factory-call form to emit.
194
+ */
195
+ function generateSolidModule(content, usedComponents, islands, frontmatter, options, id) {
196
+ const rawHtml = JSON.stringify(content);
197
+ const frontmatterLiteral = JSON.stringify(frontmatter);
198
+ if (islands.length === 0) return `
199
+ export const frontmatter = ${frontmatterLiteral};
200
+
201
+ const rawHtml = ${rawHtml};
202
+
203
+ export default function MarkdownContent() {
204
+ return <div class="ox-content" innerHTML={rawHtml} />;
205
+ }
206
+ `;
207
+ return `
208
+ import { onCleanup, onMount } from 'solid-js';
209
+ import { render } from 'solid-js/web';
210
+ import { initIslands } from '@ox-content/islands';
211
+ ${renderComponentImports(usedComponents, options, id)}
212
+
213
+ export const frontmatter = ${frontmatterLiteral};
214
+
215
+ const rawHtml = ${rawHtml};
216
+ const components = {
217
+ ${usedComponents.map((name) => ` ${name},`).join("\n")}
218
+ };
219
+
220
+ function createSolidHydrate() {
221
+ return (element, props) => {
222
+ const componentName = element.dataset.oxIsland;
223
+ const Component = components[componentName];
224
+ if (!Component) return;
225
+
226
+ // Read the slot content before clearing: the island element still holds the
227
+ // markup the Markdown transform left behind.
228
+ const islandContent = element.dataset.oxContent || element.innerHTML;
229
+ element.innerHTML = '';
230
+
231
+ const dispose = render(
232
+ () =>
233
+ islandContent
234
+ ? <Component {...props}><div innerHTML={islandContent} /></Component>
235
+ : <Component {...props} />,
236
+ element,
237
+ );
238
+
239
+ return () => dispose();
240
+ };
241
+ }
242
+
243
+ export default function MarkdownContent() {
244
+ let container;
245
+
246
+ onMount(() => {
247
+ if (!container) return;
248
+ const controller = initIslands(createSolidHydrate(), {
249
+ selector: '.ox-content [data-ox-island]',
250
+ });
251
+ onCleanup(() => controller.destroy());
252
+ });
253
+
254
+ return <div class="ox-content" ref={container} innerHTML={rawHtml} />;
255
+ }
256
+ `;
257
+ }
258
+ /** Rewrites registered component paths as imports relative to the Markdown file. */
259
+ function renderComponentImports(usedComponents, options, id) {
260
+ const mdDir = path.dirname(id);
261
+ const root = options.root || process.cwd();
262
+ return usedComponents.map((name) => {
263
+ const componentPath = options.components[name];
264
+ if (!componentPath) return "";
265
+ const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
266
+ const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
267
+ return `import ${name} from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';`;
268
+ }).filter(Boolean).join("\n");
269
+ }
270
+ //#endregion
271
+ //#region src/markdown.ts
272
+ const COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>([\s\S]*?)<\/\1>)/g;
273
+ const PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
274
+ const ISLAND_MARKER_PREFIX = "OXCONTENT-ISLAND-";
275
+ const ISLAND_MARKER_SUFFIX = "-PLACEHOLDER";
276
+ /**
277
+ * Replaces every registered component tag with a placeholder marker that
278
+ * survives Markdown rendering, so the island can be re-attached to the produced
279
+ * HTML afterwards. Tags inside fenced code blocks are left as literal text.
280
+ */
281
+ function scanComponents(markdown, components) {
282
+ const usedComponents = [];
283
+ const islands = [];
284
+ const fenceRanges = collectFenceRanges(markdown);
285
+ let islandIndex = 0;
286
+ let content = "";
287
+ let lastIndex = 0;
288
+ let match;
289
+ COMPONENT_REGEX.lastIndex = 0;
290
+ while ((match = COMPONENT_REGEX.exec(markdown)) !== null) {
291
+ const [fullMatch, componentName, propsString, rawIslandContent] = match;
292
+ const matchStart = match.index;
293
+ const matchEnd = matchStart + fullMatch.length;
294
+ if (!Object.prototype.hasOwnProperty.call(components, componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
295
+ content += markdown.slice(lastIndex, matchEnd);
296
+ lastIndex = matchEnd;
297
+ continue;
298
+ }
299
+ if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
300
+ const islandId = `ox-island-${islandIndex++}`;
301
+ islands.push({
302
+ name: componentName,
303
+ props: parseProps(propsString),
304
+ position: matchStart,
305
+ id: islandId,
306
+ content: typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0
307
+ });
308
+ content += markdown.slice(lastIndex, matchStart) + createIslandMarker(islandId);
309
+ lastIndex = matchEnd;
310
+ }
311
+ content += markdown.slice(lastIndex);
312
+ return {
313
+ content,
314
+ islands,
315
+ usedComponents
316
+ };
317
+ }
318
+ /** Swaps the placeholder markers in rendered HTML for island mount points. */
319
+ function injectIslandMarkers(html, islands) {
320
+ let output = html;
321
+ for (const island of islands) {
322
+ const marker = createIslandMarker(island.id);
323
+ const propsAttr = Object.keys(island.props).length > 0 ? ` data-ox-props='${JSON.stringify(island.props).replace(/'/g, "&#39;")}'` : "";
324
+ const contentAttr = island.content ? ` data-ox-content='${island.content.replace(/'/g, "&#39;")}'` : "";
325
+ const attrs = `data-ox-island="${island.name}"${propsAttr}${contentAttr}`;
326
+ output = output.replaceAll(`<p>${marker}</p>`, `<div ${attrs}></div>`);
327
+ output = output.replaceAll(marker, `<span ${attrs}></span>`);
328
+ }
329
+ return output;
330
+ }
331
+ function extractFrontmatter(content) {
332
+ const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
333
+ if (!match) return {
334
+ content,
335
+ frontmatter: {}
336
+ };
337
+ const frontmatter = {};
338
+ for (const line of match[1].split("\n")) {
339
+ const colonIndex = line.indexOf(":");
340
+ if (colonIndex > 0) {
341
+ const key = line.slice(0, colonIndex).trim();
342
+ let value = line.slice(colonIndex + 1).trim();
343
+ try {
344
+ value = JSON.parse(value);
345
+ } catch {
346
+ if (typeof value === "string" && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
347
+ }
348
+ frontmatter[key] = value;
349
+ }
350
+ }
351
+ return {
352
+ content: content.slice(match[0].length),
353
+ frontmatter
354
+ };
355
+ }
356
+ function createIslandMarker(islandId) {
357
+ return `${ISLAND_MARKER_PREFIX}${islandId}${ISLAND_MARKER_SUFFIX}`;
358
+ }
359
+ function collectFenceRanges(content) {
360
+ const ranges = [];
361
+ let inFence = false;
362
+ let fenceChar = "";
363
+ let fenceLength = 0;
364
+ let fenceStart = 0;
365
+ let pos = 0;
366
+ while (pos < content.length) {
367
+ const lineEnd = content.indexOf("\n", pos);
368
+ const next = lineEnd === -1 ? content.length : lineEnd + 1;
369
+ const fenceMatch = content.slice(pos, lineEnd === -1 ? content.length : lineEnd).match(/^\s{0,3}([`~]{3,})/);
370
+ if (fenceMatch) {
371
+ const marker = fenceMatch[1];
372
+ if (!inFence) {
373
+ inFence = true;
374
+ fenceChar = marker[0];
375
+ fenceLength = marker.length;
376
+ fenceStart = pos;
377
+ } else if (marker[0] === fenceChar && marker.length >= fenceLength) {
378
+ inFence = false;
379
+ ranges.push({
380
+ start: fenceStart,
381
+ end: next
382
+ });
383
+ fenceChar = "";
384
+ fenceLength = 0;
385
+ }
386
+ }
387
+ pos = next;
388
+ }
389
+ if (inFence) ranges.push({
390
+ start: fenceStart,
391
+ end: content.length
392
+ });
393
+ return ranges;
394
+ }
395
+ function isInRanges(start, end, ranges) {
396
+ for (const range of ranges) if (start < range.end && end > range.start) return true;
397
+ return false;
398
+ }
399
+ function parseProps(propsString) {
400
+ const props = {};
401
+ if (!propsString) return props;
402
+ PROP_REGEX.lastIndex = 0;
403
+ let match;
404
+ while ((match = PROP_REGEX.exec(propsString)) !== null) {
405
+ const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
406
+ if (!name) continue;
407
+ if (doubleQuoted !== void 0) props[name] = doubleQuoted;
408
+ else if (singleQuoted !== void 0) props[name] = singleQuoted;
409
+ else if (braceValue !== void 0) try {
410
+ props[name] = JSON.parse(braceValue);
411
+ } catch {
412
+ props[name] = braceValue;
413
+ }
414
+ else if (bracketValue !== void 0) try {
415
+ props[name] = JSON.parse(`[${bracketValue}]`);
416
+ } catch {
417
+ props[name] = bracketValue;
418
+ }
419
+ else props[name] = true;
420
+ }
421
+ return props;
422
+ }
423
+ //#endregion
424
+ //#region src/transform.ts
425
+ async function transformMarkdownWithSolid(code, id, options) {
426
+ const { content: markdownContent, frontmatter } = options.frontmatter ? extractFrontmatter(code) : {
427
+ content: code,
428
+ frontmatter: {}
429
+ };
430
+ const scanned = scanComponents(markdownContent, options.components);
431
+ return {
432
+ code: generateSolidModule(injectIslandMarkers((await transformMarkdown(scanned.content, id, createBaseOptions(options))).html, scanned.islands), scanned.usedComponents, scanned.islands, frontmatter, options, id),
433
+ map: null,
434
+ usedComponents: scanned.usedComponents,
435
+ frontmatter
436
+ };
437
+ }
438
+ /**
439
+ * Options handed to the core Markdown transform.
440
+ *
441
+ * The site-level features (SSG, search, OG images, highlighting) are turned off
442
+ * here: this path only produces the HTML that gets embedded in a Solid module,
443
+ * and the host app owns everything around it. Frontmatter is stripped before
444
+ * this point, so the core parser sees a body-only document.
445
+ */
446
+ function createBaseOptions(options) {
447
+ return {
448
+ srcDir: options.srcDir,
449
+ outDir: options.outDir,
450
+ base: options.base,
451
+ extensions: options.extensions,
452
+ ssg: {
453
+ enabled: false,
454
+ extension: ".html",
455
+ clean: false,
456
+ bare: false,
457
+ generateOgImage: false,
458
+ lastUpdated: false
459
+ },
460
+ gfm: options.gfm,
461
+ frontmatter: false,
462
+ toc: options.toc,
463
+ tocMaxDepth: options.tocMaxDepth,
464
+ codeAnnotations: options.codeAnnotations,
465
+ footnotes: true,
466
+ tables: true,
467
+ taskLists: true,
468
+ strikethrough: true,
469
+ autolinks: options.autolinks,
470
+ highlight: false,
471
+ highlightTheme: "github-dark",
472
+ highlightLangs: [],
473
+ mermaid: false,
474
+ ogImage: false,
475
+ ogImageOptions: {
476
+ vuePlugin: "vitejs",
477
+ width: 1200,
478
+ height: 630,
479
+ cache: true,
480
+ concurrency: 1
481
+ },
482
+ transformers: [],
483
+ docs: false,
484
+ ogViewer: false,
485
+ search: {
486
+ enabled: false,
487
+ limit: 10,
488
+ prefix: true,
489
+ placeholder: "Search...",
490
+ hotkey: "k"
491
+ },
492
+ embeds: options.embeds,
493
+ i18n: false
494
+ };
495
+ }
496
+ //#endregion
497
+ //#region src/verify.ts
498
+ const TRANSFORM_PLUGIN_NAME = "ox-content:solid-transform";
499
+ const SOLID_PLUGIN_NAME = "solid";
500
+ function formatSolidPluginError(reason) {
501
+ const example = [
502
+ " plugins: [",
503
+ " oxContentSolid({ srcDir: 'docs' }),",
504
+ " solid({ extensions: ['.md', '.markdown', '.mdx'] }),",
505
+ " ]"
506
+ ].join("\n");
507
+ return `[ox-content:solid] ${{
508
+ missing: "vite-plugin-solid was not found in the Vite config. Markdown files are emitted as Solid JSX, which only runs after babel-preset-solid compiles it.",
509
+ ordering: "vite-plugin-solid runs before oxContentSolid(), so it sees raw Markdown instead of the generated JSX. Both plugins are `enforce: 'pre'`, so their order follows the `plugins` array.",
510
+ extensions: "vite-plugin-solid did not compile the generated Markdown module. Its `extensions` option must list the Markdown extensions; by default it only looks at .jsx/.tsx files."
511
+ }[reason]}\n\n${example}\n`;
512
+ }
513
+ /**
514
+ * Throws when `vite-plugin-solid` is absent, or placed where it would see raw
515
+ * Markdown instead of the JSX this plugin generates.
516
+ */
517
+ function verifySolidPluginOrder(config) {
518
+ const names = config.plugins.map((plugin) => plugin.name);
519
+ const solidIndex = names.indexOf(SOLID_PLUGIN_NAME);
520
+ if (solidIndex === -1) throw new Error(formatSolidPluginError("missing"));
521
+ const transformIndex = names.indexOf(TRANSFORM_PLUGIN_NAME);
522
+ if (transformIndex !== -1 && solidIndex < transformIndex) throw new Error(formatSolidPluginError("ordering"));
523
+ }
524
+ //#endregion
525
+ //#region src/index.ts
526
+ /**
527
+ * Creates the Ox Content Solid integration plugin.
528
+ *
529
+ * Unlike the React and Svelte integrations, this plugin must be listed **before**
530
+ * `vite-plugin-solid`, and that plugin must be told about the Markdown
531
+ * extensions. Markdown is turned into Solid JSX here, and Solid's JSX is
532
+ * compile-time only — `vite-plugin-solid` is what turns it into DOM or SSR
533
+ * instructions.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * // vite.config.ts
538
+ * import { defineConfig } from 'vite';
539
+ * import solid from 'vite-plugin-solid';
540
+ * import { oxContentSolid } from '@ox-content/vite-plugin-solid';
541
+ *
542
+ * export default defineConfig({
543
+ * plugins: [
544
+ * oxContentSolid({
545
+ * srcDir: 'docs',
546
+ * components: {
547
+ * Counter: './src/components/Counter.tsx',
548
+ * },
549
+ * }),
550
+ * solid({ extensions: ['.md', '.markdown', '.mdx'] }),
551
+ * ],
552
+ * });
553
+ * ```
554
+ */
555
+ function oxContentSolid(options = {}) {
556
+ const resolved = resolveSolidOptions(options);
557
+ let componentMap = /* @__PURE__ */ new Map();
558
+ let config;
559
+ if (typeof options.components === "object" && !Array.isArray(options.components)) componentMap = new Map(Object.entries(options.components));
560
+ const solidTransformPlugin = {
561
+ name: TRANSFORM_PLUGIN_NAME,
562
+ enforce: "pre",
563
+ async configResolved(resolvedConfig) {
564
+ config = resolvedConfig;
565
+ if (resolved.verifySolidPlugin) verifySolidPluginOrder(resolvedConfig);
566
+ const componentsOption = options.components;
567
+ if (componentsOption) {
568
+ const resolvedComponents = await resolveComponentsGlob(componentsOption, config.root);
569
+ componentMap = new Map(Object.entries(resolvedComponents));
570
+ }
571
+ },
572
+ async transform(code, id) {
573
+ if (!isMarkdownFilePath(id, resolved.extensions)) return null;
574
+ const result = await transformMarkdownWithSolid(code, id, {
575
+ ...resolved,
576
+ components: Object.fromEntries(componentMap),
577
+ root: config.root
578
+ });
579
+ return {
580
+ code: result.code,
581
+ map: result.map
582
+ };
583
+ }
584
+ };
585
+ const solidVerifyPlugin = {
586
+ name: "ox-content:solid-verify",
587
+ enforce: "post",
588
+ transform(code, id) {
589
+ if (!resolved.verifySolidPlugin) return null;
590
+ if (!isMarkdownFilePath(id, resolved.extensions)) return null;
591
+ if (!code.includes("innerHTML={rawHtml}")) return null;
592
+ this.error(formatSolidPluginError("extensions"));
593
+ }
594
+ };
595
+ const solidEnvironmentPlugin = {
596
+ name: "ox-content:solid-environment",
597
+ config() {
598
+ const envOptions = {
599
+ ...resolved,
600
+ components: Object.fromEntries(componentMap)
601
+ };
602
+ return { environments: {
603
+ oxcontent_ssr: createSolidMarkdownEnvironment("ssr", envOptions),
604
+ oxcontent_client: createSolidMarkdownEnvironment("client", envOptions)
605
+ } };
606
+ },
607
+ resolveId(id) {
608
+ if (id === "virtual:ox-content-solid/components") return "\0virtual:ox-content-solid/components";
609
+ return null;
610
+ },
611
+ load(id) {
612
+ if (id === "\0virtual:ox-content-solid/components") return generateComponentsModule(componentMap);
613
+ return null;
614
+ },
615
+ applyToEnvironment(environment) {
616
+ return [
617
+ "oxcontent_ssr",
618
+ "oxcontent_client",
619
+ "client",
620
+ "ssr"
621
+ ].includes(environment.name);
622
+ }
623
+ };
624
+ const solidHmrPlugin = {
625
+ name: "ox-content:solid-hmr",
626
+ apply: "serve",
627
+ handleHotUpdate({ file, server, modules }) {
628
+ if (Array.from(componentMap.values()).some((path) => file.endsWith(path.replace(/^\.\//, "")))) {
629
+ const mdModules = Array.from(server.moduleGraph.idToModuleMap.values()).filter((mod) => mod.file && isMarkdownFilePath(mod.file, resolved.extensions));
630
+ if (mdModules.length > 0) {
631
+ server.ws.send({
632
+ type: "custom",
633
+ event: "ox-content:solid-update",
634
+ data: { file }
635
+ });
636
+ return [...modules, ...mdModules];
637
+ }
638
+ }
639
+ return modules;
640
+ }
641
+ };
642
+ const environmentPlugin = oxContent$1(options).flatMap((plugin) => Array.isArray(plugin) ? plugin : [plugin]).find((plugin) => plugin.name === "ox-content:environment");
643
+ const plugins = [
644
+ solidTransformPlugin,
645
+ solidVerifyPlugin,
646
+ solidEnvironmentPlugin,
647
+ solidHmrPlugin
648
+ ];
649
+ if (environmentPlugin) plugins.push(environmentPlugin);
650
+ return plugins;
651
+ }
652
+ function generateComponentsModule(componentMap) {
653
+ const imports = [];
654
+ const exports = [];
655
+ componentMap.forEach((path, name) => {
656
+ imports.push(`import ${name} from '${path}';`);
657
+ exports.push(` ${name},`);
658
+ });
659
+ return `
660
+ ${imports.join("\n")}
661
+
662
+ export const components = {
663
+ ${exports.join("\n")}
664
+ };
665
+
666
+ export default components;
667
+ `;
668
+ }
669
+ //#endregion
670
+ export { oxContent, oxContentSolid };
671
+
672
+ //# sourceMappingURL=index.mjs.map