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