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