@ox-content/vite-plugin-svelte 2.90.0 → 3.0.0-alpha.10

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 CHANGED
@@ -32,53 +32,38 @@ const COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>([\s\S]*?)<\/
32
32
  const PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
33
33
  const ISLAND_MARKER_PREFIX = "OXCONTENT-ISLAND-";
34
34
  const ISLAND_MARKER_SUFFIX = "-PLACEHOLDER";
35
+ const DOCUMENT_PROP_MARKER_PREFIX = "OXCONTENT-DOCUMENT-PROP-";
36
+ const DOCUMENT_PROP_MARKER_SUFFIX = "-PLACEHOLDER";
37
+ const PAYLOAD_SCRIPT = /^\s*<script type="application\/json">[\s\S]*?<\/script>/i;
38
+ const RUST_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
39
+ "props",
40
+ "expressions",
41
+ "spreads"
42
+ ]);
35
43
  async function transformMarkdownWithSvelte(code, id, options) {
36
44
  const components = options.components;
37
- const usedComponents = [];
38
- const islands = [];
39
- let islandIndex = 0;
40
45
  const { content: markdownContent, frontmatter } = extractFrontmatter(code);
41
- const fenceRanges = collectFenceRanges(markdownContent);
42
- let processedContent = "";
43
- let lastIndex = 0;
44
- let match;
45
- COMPONENT_REGEX.lastIndex = 0;
46
- while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
47
- const [fullMatch, componentName, propsString, rawIslandContent] = match;
48
- const matchStart = match.index;
49
- const matchEnd = matchStart + fullMatch.length;
50
- if (!Object.prototype.hasOwnProperty.call(components, componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
51
- processedContent += markdownContent.slice(lastIndex, matchEnd);
52
- lastIndex = matchEnd;
53
- continue;
54
- }
55
- if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
56
- const props = parseProps(propsString);
57
- const islandId = `ox-island-${islandIndex++}`;
58
- const islandContent = typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0;
59
- islands.push({
60
- name: componentName,
61
- props,
62
- position: matchStart,
63
- id: islandId,
64
- content: islandContent
65
- });
66
- processedContent += markdownContent.slice(lastIndex, matchStart) + createIslandMarker(islandId);
67
- lastIndex = matchEnd;
68
- }
69
- processedContent += markdownContent.slice(lastIndex);
46
+ const mdx = (0, _ox_content_vite_plugin.resolveMdxForFilePath)(id, options.mdx);
70
47
  const baseOptions = {
71
48
  srcDir: options.srcDir,
72
49
  outDir: options.outDir,
73
50
  base: options.base,
74
51
  extensions: options.extensions,
52
+ mdx,
75
53
  ssg: {
76
54
  enabled: false,
77
55
  extension: ".html",
78
56
  clean: false,
79
57
  bare: false,
80
58
  generateOgImage: false,
81
- lastUpdated: false
59
+ lastUpdated: false,
60
+ pagination: false,
61
+ breadcrumbs: false,
62
+ jsonLd: false,
63
+ readerChrome: false,
64
+ localeSwitcher: false,
65
+ a11y: false,
66
+ pageChrome: false
82
67
  },
83
68
  gfm: options.gfm,
84
69
  frontmatter: false,
@@ -91,8 +76,6 @@ async function transformMarkdownWithSvelte(code, id, options) {
91
76
  strikethrough: true,
92
77
  autolinks: options.autolinks,
93
78
  highlight: false,
94
- highlightTheme: "github-dark",
95
- highlightLangs: [],
96
79
  mermaid: false,
97
80
  ogImage: false,
98
81
  ogImageOptions: {
@@ -115,11 +98,66 @@ async function transformMarkdownWithSvelte(code, id, options) {
115
98
  embeds: options.embeds,
116
99
  i18n: false
117
100
  };
118
- const svelteCode = generateSvelteModule(injectIslandMarkers((await (0, _ox_content_vite_plugin.transformMarkdown)(processedContent, id, baseOptions)).html, islands), usedComponents, islands, frontmatter, options, id);
101
+ if (mdx) {
102
+ const documentExpressions = options.mdxDocumentProps ? prepareMdxDocumentExpressions(markdownContent, id) : {
103
+ content: markdownContent,
104
+ expressions: []
105
+ };
106
+ const transformed = await (0, _ox_content_vite_plugin.transformMarkdown)(documentExpressions.content, id, baseOptions);
107
+ const discovered = await (0, _ox_content_vite_plugin.discoverDocumentMdxIslands)({
108
+ source: markdownContent,
109
+ html: transformed.html,
110
+ components,
111
+ imports: transformed.imports,
112
+ documentPath: id,
113
+ contentRoot: (0, _ox_content_vite_plugin.resolveContentRootPath)({
114
+ srcDir: options.srcDir,
115
+ root: options.root
116
+ }),
117
+ srcDir: options.srcDir
118
+ });
119
+ if (options.mdxDocumentProps) return compileSvelteResult(generateMdxDocumentPropsSvelteModule(transformed.html, discovered.usedComponents, frontmatter, options, id, discovered.localBindings, documentExpressions.expressions), id, discovered.usedComponents, frontmatter, options.ssr);
120
+ return compileSvelteResult(generateSvelteModule(options.renderIsland ? await (0, _ox_content_vite_plugin.applyIslandSsrHtml)(transformed.html, options.renderIsland, id, discovered.usedComponents) : transformed.html, discovered.usedComponents, discovered.usedComponents, frontmatter, options, id, discovered.localBindings), id, discovered.usedComponents, frontmatter, options.ssr);
121
+ }
122
+ const usedComponents = [];
123
+ const islands = [];
124
+ let islandIndex = 0;
125
+ const fenceRanges = collectFenceRanges(markdownContent);
126
+ let processedContent = "";
127
+ let lastIndex = 0;
128
+ let match;
129
+ COMPONENT_REGEX.lastIndex = 0;
130
+ while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
131
+ const [fullMatch, componentName, propsString, rawIslandContent] = match;
132
+ const matchStart = match.index;
133
+ const matchEnd = matchStart + fullMatch.length;
134
+ if (!Object.prototype.hasOwnProperty.call(components, componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
135
+ processedContent += markdownContent.slice(lastIndex, matchEnd);
136
+ lastIndex = matchEnd;
137
+ continue;
138
+ }
139
+ if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
140
+ const props = parseProps(propsString);
141
+ const islandId = `ox-island-${islandIndex++}`;
142
+ const islandContent = typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0;
143
+ islands.push({
144
+ name: componentName,
145
+ props,
146
+ position: matchStart,
147
+ id: islandId,
148
+ content: islandContent
149
+ });
150
+ processedContent += markdownContent.slice(lastIndex, matchStart) + createIslandMarker(islandId);
151
+ lastIndex = matchEnd;
152
+ }
153
+ processedContent += markdownContent.slice(lastIndex);
154
+ return compileSvelteResult(generateSvelteModule(injectIslandMarkers((await (0, _ox_content_vite_plugin.transformMarkdown)(processedContent, id, baseOptions)).html, islands), usedComponents, islands, frontmatter, options, id), id, usedComponents, frontmatter, options.ssr);
155
+ }
156
+ function compileSvelteResult(svelteCode, id, usedComponents, frontmatter, ssr = false) {
119
157
  return {
120
158
  code: `${(0, svelte_compiler.compile)(svelteCode, {
121
159
  filename: id,
122
- generate: "client",
160
+ generate: ssr ? "server" : "client",
123
161
  runes: true
124
162
  }).js.code}\nexport const frontmatter = ${JSON.stringify(frontmatter)};`,
125
163
  map: null,
@@ -215,36 +253,473 @@ function parseProps(propsString) {
215
253
  let match;
216
254
  while ((match = PROP_REGEX.exec(propsString)) !== null) {
217
255
  const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
218
- if (name) if (doubleQuoted !== void 0) props[name] = doubleQuoted;
219
- else if (singleQuoted !== void 0) props[name] = singleQuoted;
220
- else if (braceValue !== void 0) try {
221
- props[name] = JSON.parse(braceValue);
222
- } catch {
223
- props[name] = braceValue;
224
- }
225
- else if (bracketValue !== void 0) try {
226
- props[name] = JSON.parse(`[${bracketValue}]`);
227
- } catch {
228
- props[name] = bracketValue;
256
+ if (name) {
257
+ if (doubleQuoted !== void 0) props[name] = doubleQuoted;
258
+ else if (singleQuoted !== void 0) props[name] = singleQuoted;
259
+ else if (braceValue !== void 0) try {
260
+ props[name] = JSON.parse(braceValue);
261
+ } catch {
262
+ props[name] = braceValue;
263
+ }
264
+ else if (bracketValue !== void 0) try {
265
+ props[name] = JSON.parse(`[${bracketValue}]`);
266
+ } catch {
267
+ props[name] = bracketValue;
268
+ }
269
+ else props[name] = true;
229
270
  }
230
- else props[name] = true;
231
271
  }
232
272
  return props;
233
273
  }
234
- function generateSvelteModule(content, usedComponents, islands, frontmatter, options, id) {
235
- const mdDir = path.dirname(id);
236
- const root = options.root || process.cwd();
237
- const imports = usedComponents.map((name) => {
238
- const componentPath = options.components[name];
239
- if (!componentPath) return "";
240
- const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
241
- const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
242
- return `import ${name} from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';`;
243
- }).filter(Boolean).join("\n");
244
- if (islands.length === 0) return `
274
+ function prepareMdxDocumentExpressions(content, filePath) {
275
+ const skipRanges = mergeRanges([
276
+ ...collectFenceRanges(content),
277
+ ...collectInlineCodeRanges(content),
278
+ ...collectMdxEsmLineRanges(content)
279
+ ]);
280
+ const expressions = [];
281
+ let output = "";
282
+ let cursor = 0;
283
+ let rangeIndex = 0;
284
+ let inTag = false;
285
+ let quote = null;
286
+ while (cursor < content.length) {
287
+ const range = skipRanges[rangeIndex];
288
+ if (range && cursor >= range.end) {
289
+ rangeIndex += 1;
290
+ continue;
291
+ }
292
+ if (range && cursor === range.start) {
293
+ output += content.slice(range.start, range.end);
294
+ cursor = range.end;
295
+ continue;
296
+ }
297
+ const char = content[cursor];
298
+ if (inTag) {
299
+ output += char;
300
+ if (quote) {
301
+ if (char === quote && content[cursor - 1] !== "\\") quote = null;
302
+ } else if (char === "\"" || char === "'") quote = char;
303
+ else if (char === ">") inTag = false;
304
+ cursor += 1;
305
+ continue;
306
+ }
307
+ if (char === "<" && startsHtmlLikeTag(content, cursor)) {
308
+ inTag = true;
309
+ output += char;
310
+ cursor += 1;
311
+ continue;
312
+ }
313
+ if (char === "{" && content[cursor - 1] !== "\\") {
314
+ const end = findMdxExpressionEnd(content, cursor + 1);
315
+ if (end !== -1) {
316
+ const expression = content.slice(cursor + 1, end).trim();
317
+ const path = parseDocumentPropPath(expression);
318
+ if (!path) throw new Error(`[ox-content-svelte] Unsupported MDX document prop expression "{${expression}}" in ${filePath}. Only identifiers and dotted property paths are supported.`);
319
+ const marker = `${DOCUMENT_PROP_MARKER_PREFIX}${expressions.length}${DOCUMENT_PROP_MARKER_SUFFIX}`;
320
+ expressions.push({
321
+ marker,
322
+ expression,
323
+ path
324
+ });
325
+ output += marker;
326
+ cursor = end + 1;
327
+ continue;
328
+ }
329
+ }
330
+ output += char;
331
+ cursor += 1;
332
+ }
333
+ return {
334
+ content: output,
335
+ expressions
336
+ };
337
+ }
338
+ function collectInlineCodeRanges(content) {
339
+ const ranges = [];
340
+ const fenceRanges = collectFenceRanges(content);
341
+ let lineStart = 0;
342
+ while (lineStart < content.length) {
343
+ const lineEnd = content.indexOf("\n", lineStart);
344
+ const end = lineEnd === -1 ? content.length : lineEnd;
345
+ if (!isInRanges(lineStart, end, fenceRanges)) {
346
+ let cursor = lineStart;
347
+ while (cursor < end) {
348
+ const marker = matchBacktickRun(content, cursor);
349
+ if (!marker) {
350
+ cursor += 1;
351
+ continue;
352
+ }
353
+ const close = content.indexOf(marker, cursor + marker.length);
354
+ if (close === -1 || close >= end) {
355
+ cursor += marker.length;
356
+ continue;
357
+ }
358
+ ranges.push({
359
+ start: cursor,
360
+ end: close + marker.length
361
+ });
362
+ cursor = close + marker.length;
363
+ }
364
+ }
365
+ lineStart = lineEnd === -1 ? content.length : lineEnd + 1;
366
+ }
367
+ return ranges;
368
+ }
369
+ function collectMdxEsmLineRanges(content) {
370
+ const ranges = [];
371
+ const fenceRanges = collectFenceRanges(content);
372
+ let lineStart = 0;
373
+ while (lineStart < content.length) {
374
+ const lineEnd = content.indexOf("\n", lineStart);
375
+ const end = lineEnd === -1 ? content.length : lineEnd + 1;
376
+ const contentEnd = lineEnd === -1 ? content.length : lineEnd;
377
+ if (!isInRanges(lineStart, contentEnd, fenceRanges)) {
378
+ const line = content.slice(lineStart, contentEnd).trimStart();
379
+ if (line.startsWith("import ") || line.startsWith("export ")) ranges.push({
380
+ start: lineStart,
381
+ end
382
+ });
383
+ }
384
+ lineStart = lineEnd === -1 ? content.length : lineEnd + 1;
385
+ }
386
+ return ranges;
387
+ }
388
+ function mergeRanges(ranges) {
389
+ const sorted = ranges.filter((range) => range.end > range.start).sort((left, right) => left.start - right.start || left.end - right.end);
390
+ const merged = [];
391
+ for (const range of sorted) {
392
+ const previous = merged.at(-1);
393
+ if (previous && range.start <= previous.end) previous.end = Math.max(previous.end, range.end);
394
+ else merged.push({ ...range });
395
+ }
396
+ return merged;
397
+ }
398
+ function matchBacktickRun(content, index) {
399
+ if (content[index] !== "`") return null;
400
+ let end = index + 1;
401
+ while (content[end] === "`") end += 1;
402
+ return content.slice(index, end);
403
+ }
404
+ function startsHtmlLikeTag(content, index) {
405
+ const next = content[index + 1];
406
+ return next === "/" || next === "!" || next === "?" || /[A-Za-z]/.test(next ?? "");
407
+ }
408
+ function findMdxExpressionEnd(content, start) {
409
+ let depth = 1;
410
+ let quote = null;
411
+ let escaped = false;
412
+ for (let index = start; index < content.length; index += 1) {
413
+ const char = content[index];
414
+ if (quote) {
415
+ if (escaped) escaped = false;
416
+ else if (char === "\\") escaped = true;
417
+ else if (char === quote) quote = null;
418
+ continue;
419
+ }
420
+ if (char === "\"" || char === "'" || char === "`") {
421
+ quote = char;
422
+ continue;
423
+ }
424
+ if (char === "{") {
425
+ depth += 1;
426
+ continue;
427
+ }
428
+ if (char === "}") {
429
+ depth -= 1;
430
+ if (depth === 0) return index;
431
+ }
432
+ }
433
+ return -1;
434
+ }
435
+ function parseDocumentPropPath(expression) {
436
+ if (!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(expression) || RESERVED_DOCUMENT_PROP_WORDS.has(expression)) return null;
437
+ return expression.split(".");
438
+ }
439
+ const RESERVED_DOCUMENT_PROP_WORDS = /* @__PURE__ */ new Set([
440
+ "false",
441
+ "Infinity",
442
+ "NaN",
443
+ "null",
444
+ "this",
445
+ "true",
446
+ "undefined"
447
+ ]);
448
+ function generateMdxDocumentPropsSvelteModule(html, usedComponents, frontmatter, options, id, localBindings, documentExpressions) {
449
+ const filePathLiteral = JSON.stringify(id);
450
+ const imports = (0, _ox_content_vite_plugin.renderIslandComponentImports)(usedComponents, {
451
+ globalComponents: options.components,
452
+ localBindings,
453
+ documentPath: id,
454
+ root: options.root
455
+ });
456
+ const template = renderMdxDocumentTemplate(html, usedComponents, id, documentExpressions);
457
+ return `
458
+ <script>
459
+ ${imports}
460
+
461
+ const frontmatter = ${JSON.stringify(frontmatter)};
462
+ export { frontmatter };
463
+
464
+ let __ox_mdx_props = $props();
465
+
466
+ function __ox_mdx_document_prop(props, path, expression) {
467
+ const propName = path.join(".");
468
+ let value = props;
469
+ for (const segment of path) {
470
+ if (
471
+ value == null ||
472
+ (typeof value !== "object" && typeof value !== "function") ||
473
+ !(segment in Object(value))
474
+ ) {
475
+ throw new Error('[ox-content-svelte] Missing MDX document prop "' + propName + '" in ' + ${filePathLiteral} + ' for expression {' + expression + '}.');
476
+ }
477
+ value = value[segment];
478
+ }
479
+ if (value === undefined) {
480
+ throw new Error('[ox-content-svelte] Missing MDX document prop "' + propName + '" in ' + ${filePathLiteral} + ' for expression {' + expression + '}.');
481
+ }
482
+ return value;
483
+ }
484
+ <\/script>
485
+
486
+ <div class="ox-content">${template}</div>
487
+
488
+ <style>
489
+ .ox-content {
490
+ line-height: 1.6;
491
+ }
492
+ </style>
493
+ `;
494
+ }
495
+ function renderMdxDocumentTemplate(html, usedComponents, filePath, documentExpressions) {
496
+ return renderHtmlRange({
497
+ html,
498
+ filePath,
499
+ usedComponents: new Set(usedComponents),
500
+ expressionsByMarker: new Map(documentExpressions.map((expression) => [expression.marker, expression])),
501
+ islandRanges: findMdxIslandRanges(html)
502
+ }, 0, html.length);
503
+ }
504
+ function renderHtmlRange(context, start, end) {
505
+ let output = "";
506
+ let cursor = start;
507
+ while (cursor < end) {
508
+ const island = findNextIslandRange(context, cursor, end);
509
+ if (!island) {
510
+ output += renderRawHtmlTemplate(context.html.slice(cursor, end), context.expressionsByMarker);
511
+ break;
512
+ }
513
+ output += renderRawHtmlTemplate(context.html.slice(cursor, island.openStart), context.expressionsByMarker);
514
+ output += renderMdxIslandTemplate(context, island);
515
+ cursor = island.closeEnd;
516
+ }
517
+ return output;
518
+ }
519
+ function findNextIslandRange(context, cursor, end) {
520
+ for (const island of context.islandRanges) {
521
+ if (island.openStart < cursor || island.closeEnd > end) continue;
522
+ if (context.usedComponents.has(island.name)) return island;
523
+ }
524
+ return null;
525
+ }
526
+ function renderRawHtmlTemplate(html, expressionsByMarker) {
527
+ if (!html) return "";
528
+ let output = "";
529
+ let cursor = 0;
530
+ while (cursor < html.length) {
531
+ const next = findNextDocumentExpressionMarker(html, cursor, expressionsByMarker);
532
+ if (!next) {
533
+ output += renderRawHtmlBlock(html.slice(cursor));
534
+ break;
535
+ }
536
+ output += renderRawHtmlBlock(html.slice(cursor, next.index));
537
+ output += renderDocumentExpression(next.expression);
538
+ cursor = next.index + next.expression.marker.length;
539
+ }
540
+ return output;
541
+ }
542
+ function findNextDocumentExpressionMarker(html, start, expressionsByMarker) {
543
+ let nextIndex = -1;
544
+ let nextExpression;
545
+ for (const expression of expressionsByMarker.values()) {
546
+ const index = html.indexOf(expression.marker, start);
547
+ if (index !== -1 && (nextIndex === -1 || index < nextIndex)) {
548
+ nextIndex = index;
549
+ nextExpression = expression;
550
+ }
551
+ }
552
+ return nextExpression ? {
553
+ index: nextIndex,
554
+ expression: nextExpression
555
+ } : null;
556
+ }
557
+ function renderRawHtmlBlock(html) {
558
+ return html ? `{@html ${JSON.stringify(html).replaceAll("<\/script", "<\\/script")}}` : "";
559
+ }
560
+ function renderDocumentExpression(expression) {
561
+ return `{${documentPropResolverExpression(expression.path, expression.expression)}}`;
562
+ }
563
+ function renderMdxIslandTemplate(context, island) {
564
+ assertSvelteComponentName(island.name, context.filePath);
565
+ const attrs = renderMdxIslandAttributes(readMdxIslandPayload(island), context.filePath);
566
+ const children = renderHtmlRange(context, island.contentStart, island.closeStart);
567
+ return children ? `<${island.name}${attrs}>${children}</${island.name}>` : `<${island.name}${attrs} />`;
568
+ }
569
+ function renderMdxIslandAttributes(payload, filePath) {
570
+ const attrs = [];
571
+ for (const spread of payload.spreads) {
572
+ const expression = spread.trim().startsWith("...") ? spread.trim().slice(3).trim() : spread.trim();
573
+ const path = parseDocumentPropPath(expression);
574
+ if (!path) throw new Error(`[ox-content-svelte] Unsupported MDX document prop spread "{${spread}}" in ${filePath}. Only identifiers and dotted property paths are supported.`);
575
+ attrs.push(`{...${documentPropResolverExpression(path, expression)}}`);
576
+ }
577
+ for (const [name, value] of Object.entries(payload.props)) {
578
+ assertSvelteAttributeName(name, filePath);
579
+ attrs.push(`${name}={${renderSvelteLiteral(value)}}`);
580
+ }
581
+ for (const [name, expression] of Object.entries(payload.expressions)) {
582
+ assertSvelteAttributeName(name, filePath);
583
+ const path = parseDocumentPropPath(expression.trim());
584
+ if (!path) throw new Error(`[ox-content-svelte] Unsupported MDX document prop expression "{${expression}}" for prop "${name}" in ${filePath}. Only identifiers and dotted property paths are supported.`);
585
+ attrs.push(`${name}={${documentPropResolverExpression(path, expression.trim())}}`);
586
+ }
587
+ return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
588
+ }
589
+ function documentPropResolverExpression(path, expression) {
590
+ return `__ox_mdx_document_prop(__ox_mdx_props, ${JSON.stringify(path)}, ${JSON.stringify(expression)})`;
591
+ }
592
+ function renderSvelteLiteral(value) {
593
+ const literal = JSON.stringify(value);
594
+ return literal === void 0 ? "undefined" : literal.replaceAll("<\/script", "<\\/script");
595
+ }
596
+ function findMdxIslandRanges(html) {
597
+ const ranges = [];
598
+ const openRe = /<(div|span)\b([^>]*\bdata-ox-island="([^"]+)"[^>]*)>/gi;
599
+ let match;
600
+ while ((match = openRe.exec(html)) !== null) {
601
+ const tag = match[1];
602
+ const name = decodeHtmlAttr(match[3] ?? "");
603
+ if (!name) continue;
604
+ const openStart = match.index;
605
+ const openEnd = match.index + match[0].length;
606
+ const closeStart = findMatchingClose(html, openEnd, tag);
607
+ const closeEnd = closeStart < html.length ? closeStart + tag.length + 3 : html.length;
608
+ const script = html.slice(openEnd, closeStart).match(PAYLOAD_SCRIPT)?.[0];
609
+ ranges.push({
610
+ name,
611
+ tag,
612
+ openStart,
613
+ openEnd,
614
+ innerStart: openEnd,
615
+ contentStart: openEnd + (script?.length ?? 0),
616
+ closeStart,
617
+ closeEnd,
618
+ propsAttr: matchAttr(match[2] ?? "", "data-ox-props"),
619
+ script
620
+ });
621
+ }
622
+ return ranges.sort((left, right) => left.openStart - right.openStart);
623
+ }
624
+ function findMatchingClose(html, from, tag) {
625
+ const openNeedle = `<${tag}`;
626
+ const closeNeedle = `</${tag}>`;
627
+ let depth = 1;
628
+ let cursor = from;
629
+ while (cursor < html.length) {
630
+ const nextOpen = indexOfTagOpen(html, openNeedle, cursor);
631
+ const nextClose = html.indexOf(closeNeedle, cursor);
632
+ if (nextClose === -1) return html.length;
633
+ if (nextOpen !== -1 && nextOpen < nextClose) {
634
+ depth += 1;
635
+ cursor = nextOpen + openNeedle.length;
636
+ } else {
637
+ depth -= 1;
638
+ if (depth === 0) return nextClose;
639
+ cursor = nextClose + closeNeedle.length;
640
+ }
641
+ }
642
+ return html.length;
643
+ }
644
+ function indexOfTagOpen(html, openNeedle, from) {
645
+ let cursor = from;
646
+ while (cursor < html.length) {
647
+ const index = html.indexOf(openNeedle, cursor);
648
+ if (index === -1) return -1;
649
+ const next = html[index + openNeedle.length];
650
+ if (next === " " || next === ">" || next === " " || next === "\n" || next === "/") return index;
651
+ cursor = index + openNeedle.length;
652
+ }
653
+ return -1;
654
+ }
655
+ function matchAttr(attrs, name) {
656
+ const match = new RegExp(`\\b${name}="([^"]*)"`, "i").exec(attrs);
657
+ return match?.[1] === void 0 ? void 0 : decodeHtmlAttr(match[1]);
658
+ }
659
+ function readMdxIslandPayload(island) {
660
+ const fromAttr = island.propsAttr ? tryParseJson(island.propsAttr) : void 0;
661
+ const fromScript = island.script ? tryParseJson(island.script.match(/<script type="application\/json">([\s\S]*?)<\/script>/i)?.[1] ?? "") : void 0;
662
+ return normalizeMdxIslandPayload(fromAttr ?? fromScript ?? {});
663
+ }
664
+ function normalizeMdxIslandPayload(parsed) {
665
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
666
+ props: {},
667
+ expressions: {},
668
+ spreads: []
669
+ };
670
+ const record = parsed;
671
+ const keys = Object.keys(record);
672
+ if (keys.length > 0 && keys.every((key) => RUST_PAYLOAD_KEYS.has(key))) return {
673
+ props: toRecord(record.props),
674
+ expressions: toStringRecord(record.expressions),
675
+ spreads: toStringArray(record.spreads)
676
+ };
677
+ return {
678
+ props: record,
679
+ expressions: {},
680
+ spreads: []
681
+ };
682
+ }
683
+ function toRecord(value) {
684
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
685
+ }
686
+ function toStringRecord(value) {
687
+ const record = toRecord(value);
688
+ const output = {};
689
+ for (const [key, entry] of Object.entries(record)) if (typeof entry === "string") output[key] = entry;
690
+ return output;
691
+ }
692
+ function toStringArray(value) {
693
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
694
+ }
695
+ function tryParseJson(value) {
696
+ try {
697
+ return JSON.parse(value);
698
+ } catch {
699
+ return;
700
+ }
701
+ }
702
+ function decodeHtmlAttr(value) {
703
+ return value.replaceAll("&quot;", "\"").replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
704
+ }
705
+ function assertSvelteComponentName(name, filePath) {
706
+ if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) throw new Error(`[ox-content-svelte] Unsupported MDX component name "${name}" in ${filePath} for mdxDocumentProps. Only simple Svelte component identifiers are supported.`);
707
+ }
708
+ function assertSvelteAttributeName(name, filePath) {
709
+ if (!/^[A-Za-z_$][\w$-]*$/.test(name)) throw new Error(`[ox-content-svelte] Unsupported MDX component prop name "${name}" in ${filePath}.`);
710
+ }
711
+ function generateSvelteModule(content, usedComponents, _islands, frontmatter, options, id, localBindings) {
712
+ const rawHtmlLiteral = JSON.stringify(content).replaceAll("<\/script", "<\\/script");
713
+ const imports = (0, _ox_content_vite_plugin.renderIslandComponentImports)(usedComponents, {
714
+ globalComponents: options.components,
715
+ localBindings,
716
+ documentPath: id,
717
+ root: options.root
718
+ });
719
+ if (usedComponents.length === 0) return `
245
720
  <script>
246
721
  const frontmatter = ${JSON.stringify(frontmatter)};
247
- const rawHtml = ${JSON.stringify(content)};
722
+ const rawHtml = ${rawHtmlLiteral};
248
723
 
249
724
  export { frontmatter };
250
725
  <\/script>
@@ -263,11 +738,11 @@ function generateSvelteModule(content, usedComponents, islands, frontmatter, opt
263
738
  return `
264
739
  <script>
265
740
  import { createRawSnippet, onMount, mount, unmount } from 'svelte';
266
- import { initIslands } from '@ox-content/islands';
741
+ import { initIslands, readIslandSlotHtml } from '@ox-content/islands';
267
742
  ${imports}
268
743
 
269
744
  const frontmatter = ${JSON.stringify(frontmatter)};
270
- const rawHtml = ${JSON.stringify(content)};
745
+ const rawHtml = ${rawHtmlLiteral};
271
746
  const components = {
272
747
  ${componentMap}
273
748
  };
@@ -284,7 +759,7 @@ ${componentMap}
284
759
  const Component = components[componentName];
285
760
  if (!Component) return;
286
761
 
287
- const islandContent = element.dataset.oxContent || element.innerHTML;
762
+ const islandContent = readIslandSlotHtml(element);
288
763
  const componentProps = { ...props };
289
764
  if (islandContent) {
290
765
  componentProps.children = createRawSnippet(() => ({
@@ -384,6 +859,10 @@ function resolveSingleEmbedOptions(options) {
384
859
  /**
385
860
  * Creates the Ox Content Svelte integration plugin.
386
861
  *
862
+ * Forwards core options such as `ssg`, `redirects`, `feeds`, and `siteMaps`.
863
+ * The Svelte Markdown transform and environments replace the generic core
864
+ * transform/`markdown` environment; other build plugins are kept.
865
+ *
387
866
  * @example
388
867
  * ```ts
389
868
  * // vite.config.ts
@@ -420,12 +899,14 @@ function oxContentSvelte(options = {}) {
420
899
  componentMap = new Map(Object.entries(resolvedComponents));
421
900
  }
422
901
  },
423
- async transform(code, id) {
902
+ async transform(code, id, transformOptions) {
424
903
  if (!isMarkdownFilePath(id, resolved.extensions)) return null;
425
904
  const result = await transformMarkdownWithSvelte(code, id, {
426
905
  ...resolved,
427
906
  components: Object.fromEntries(componentMap),
428
- root: config.root
907
+ root: config.root,
908
+ renderIsland: options.renderIsland,
909
+ ssr: transformOptions?.ssr
429
910
  });
430
911
  return {
431
912
  code: result.code,
@@ -478,14 +959,13 @@ function oxContentSvelte(options = {}) {
478
959
  return modules;
479
960
  }
480
961
  };
481
- const environmentPlugin = (0, _ox_content_vite_plugin.oxContent)(options).flatMap((plugin) => Array.isArray(plugin) ? plugin : [plugin]).find((plugin) => plugin.name === "ox-content:environment");
482
- const plugins = [
962
+ const replacedCorePluginNames = /* @__PURE__ */ new Set(["ox-content", "ox-content:environment"]);
963
+ return [
483
964
  svelteTransformPlugin,
484
965
  svelteEnvironmentPlugin,
485
- svelteHmrPlugin
966
+ svelteHmrPlugin,
967
+ ...(0, _ox_content_vite_plugin.oxContent)(options).flatMap((plugin) => Array.isArray(plugin) ? plugin : [plugin]).filter((plugin) => !replacedCorePluginNames.has(plugin.name))
486
968
  ];
487
- if (environmentPlugin) plugins.push(environmentPlugin);
488
- return plugins;
489
969
  }
490
970
  function resolveSvelteOptions(options) {
491
971
  return {
@@ -500,7 +980,9 @@ function resolveSvelteOptions(options) {
500
980
  tocMaxDepth: options.tocMaxDepth ?? 3,
501
981
  codeAnnotations: resolveCodeAnnotationsOptions(options.codeAnnotations),
502
982
  runes: options.runes ?? true,
503
- embeds: resolveBuiltinEmbedOptions(options.embeds)
983
+ embeds: resolveBuiltinEmbedOptions(options.embeds),
984
+ mdx: options.mdx,
985
+ mdxDocumentProps: options.mdxDocumentProps ?? false
504
986
  };
505
987
  }
506
988
  function resolveCodeAnnotationsOptions(options) {
@@ -590,3 +1072,9 @@ Object.defineProperty(exports, "oxContent", {
590
1072
  }
591
1073
  });
592
1074
  exports.oxContentSvelte = oxContentSvelte;
1075
+ Object.defineProperty(exports, "renderHead", {
1076
+ enumerable: true,
1077
+ get: function() {
1078
+ return _ox_content_vite_plugin.renderHead;
1079
+ }
1080
+ });