@ox-content/vite-plugin-vue 0.0.1-alpha.0 → 0.3.0-alpha.11

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
@@ -1,400 +1,501 @@
1
- "use strict";
1
+ //#region rolldown:runtime
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
8
  var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ 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) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
19
21
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
29
26
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- oxContent: () => import_vite_plugin_ox_content3.oxContent,
34
- oxContentVue: () => oxContentVue
35
- });
36
- module.exports = __toCommonJS(index_exports);
37
- var fs = __toESM(require("fs"), 1);
38
- var path2 = __toESM(require("path"), 1);
39
- var import_vite_plugin_ox_content2 = require("vite-plugin-ox-content");
27
+ //#endregion
28
+ let fs = require("fs");
29
+ fs = __toESM(fs);
30
+ let path$1 = require("path");
31
+ path$1 = __toESM(path$1);
32
+ let _ox_content_vite_plugin = require("@ox-content/vite-plugin");
40
33
 
41
- // src/transform.ts
42
- var path = __toESM(require("path"), 1);
43
- var import_vite_plugin_ox_content = require("vite-plugin-ox-content");
44
- var COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>(?:[\s\S]*?)<\/\1>)/g;
45
- var PROP_REGEX = /(?::|v-bind:)?([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
34
+ //#region src/transform.ts
35
+ /**
36
+ * Markdown to Vue SFC transformation.
37
+ */
38
+ const COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>([\s\S]*?)<\/\1>)/g;
39
+ const PROP_REGEX = /(?::|v-bind:)?([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
40
+ const ISLAND_MARKER_PREFIX = "OXCONTENT-ISLAND-";
41
+ const ISLAND_MARKER_SUFFIX = "-PLACEHOLDER";
42
+ /**
43
+ * Transforms Markdown content with Vue component support.
44
+ */
46
45
  async function transformMarkdownWithVue(code, id, options) {
47
- const { components } = options;
48
- const usedComponents = [];
49
- const slots = [];
50
- let slotIndex = 0;
51
- const { content: markdownContent, frontmatter } = extractFrontmatter(code);
52
- let processedContent = markdownContent;
53
- let match;
54
- while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
55
- const [fullMatch, componentName, propsString] = match;
56
- if (components.has(componentName)) {
57
- if (!usedComponents.includes(componentName)) {
58
- usedComponents.push(componentName);
59
- }
60
- const props = parseProps(propsString);
61
- const slotId = `__ox_slot_${slotIndex++}__`;
62
- slots.push({
63
- name: componentName,
64
- props,
65
- position: match.index,
66
- id: slotId
67
- });
68
- processedContent = processedContent.replace(
69
- fullMatch,
70
- `<div data-ox-slot="${slotId}"></div>`
71
- );
72
- }
73
- }
74
- const transformed = await (0, import_vite_plugin_ox_content.transformMarkdown)(processedContent, id, {
75
- srcDir: options.srcDir,
76
- outDir: options.outDir,
77
- base: options.base,
78
- ssg: { enabled: false, extension: ".html", clean: false, bare: false, generateOgImage: false },
79
- gfm: options.gfm,
80
- frontmatter: false,
81
- // Already extracted
82
- toc: options.toc,
83
- tocMaxDepth: options.tocMaxDepth,
84
- footnotes: true,
85
- tables: true,
86
- taskLists: true,
87
- strikethrough: true,
88
- highlight: false,
89
- highlightTheme: "github-dark",
90
- mermaid: false,
91
- ogImage: false,
92
- ogImageOptions: {},
93
- transformers: [],
94
- docs: false,
95
- search: { enabled: false, limit: 10, prefix: true, placeholder: "Search...", hotkey: "k" }
96
- });
97
- const sfcCode = generateVueSFC(
98
- transformed.html,
99
- usedComponents,
100
- slots,
101
- frontmatter,
102
- options,
103
- id
104
- );
105
- return {
106
- code: sfcCode,
107
- map: null,
108
- usedComponents,
109
- frontmatter
110
- };
46
+ const { components } = options;
47
+ const usedComponents = [];
48
+ const islands = [];
49
+ let islandIndex = 0;
50
+ const { content: markdownContent, frontmatter } = extractFrontmatter(code);
51
+ const fenceRanges = collectFenceRanges(markdownContent);
52
+ let processedContent = "";
53
+ let lastIndex = 0;
54
+ let match;
55
+ COMPONENT_REGEX.lastIndex = 0;
56
+ while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
57
+ const [fullMatch, componentName, propsString, rawIslandContent] = match;
58
+ const matchStart = match.index;
59
+ const matchEnd = matchStart + fullMatch.length;
60
+ if (!components.has(componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
61
+ processedContent += markdownContent.slice(lastIndex, matchEnd);
62
+ lastIndex = matchEnd;
63
+ continue;
64
+ }
65
+ if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
66
+ const props = parseProps(propsString);
67
+ const islandId = `ox-island-${islandIndex++}`;
68
+ const islandContent = typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0;
69
+ islands.push({
70
+ name: componentName,
71
+ props,
72
+ position: matchStart,
73
+ id: islandId,
74
+ content: islandContent
75
+ });
76
+ processedContent += markdownContent.slice(lastIndex, matchStart) + createIslandMarker(islandId);
77
+ lastIndex = matchEnd;
78
+ }
79
+ processedContent += markdownContent.slice(lastIndex);
80
+ return {
81
+ code: generateVueSFC(injectIslandMarkers((await (0, _ox_content_vite_plugin.transformMarkdown)(processedContent, id, {
82
+ srcDir: options.srcDir,
83
+ outDir: options.outDir,
84
+ base: options.base,
85
+ ssg: {
86
+ enabled: false,
87
+ extension: ".html",
88
+ clean: false,
89
+ bare: false,
90
+ generateOgImage: false
91
+ },
92
+ gfm: options.gfm,
93
+ frontmatter: false,
94
+ toc: options.toc,
95
+ tocMaxDepth: options.tocMaxDepth,
96
+ footnotes: true,
97
+ tables: true,
98
+ taskLists: true,
99
+ strikethrough: true,
100
+ highlight: false,
101
+ highlightTheme: "github-dark",
102
+ mermaid: false,
103
+ ogImage: false,
104
+ ogImageOptions: {},
105
+ transformers: [],
106
+ docs: false,
107
+ search: {
108
+ enabled: false,
109
+ limit: 10,
110
+ prefix: true,
111
+ placeholder: "Search...",
112
+ hotkey: "k"
113
+ }
114
+ })).html, islands), usedComponents, islands, frontmatter, options, id),
115
+ map: null,
116
+ usedComponents,
117
+ frontmatter
118
+ };
119
+ }
120
+ function createIslandMarker(islandId) {
121
+ return `${ISLAND_MARKER_PREFIX}${islandId}${ISLAND_MARKER_SUFFIX}`;
122
+ }
123
+ function collectFenceRanges(content) {
124
+ const ranges = [];
125
+ let inFence = false;
126
+ let fenceChar = "";
127
+ let fenceLength = 0;
128
+ let fenceStart = 0;
129
+ let pos = 0;
130
+ while (pos < content.length) {
131
+ const lineEnd = content.indexOf("\n", pos);
132
+ const next = lineEnd === -1 ? content.length : lineEnd + 1;
133
+ const fenceMatch = content.slice(pos, lineEnd === -1 ? content.length : lineEnd).match(/^\s{0,3}([`~]{3,})/);
134
+ if (fenceMatch) {
135
+ const marker = fenceMatch[1];
136
+ if (!inFence) {
137
+ inFence = true;
138
+ fenceChar = marker[0];
139
+ fenceLength = marker.length;
140
+ fenceStart = pos;
141
+ } else if (marker[0] === fenceChar && marker.length >= fenceLength) {
142
+ inFence = false;
143
+ ranges.push({
144
+ start: fenceStart,
145
+ end: next
146
+ });
147
+ fenceChar = "";
148
+ fenceLength = 0;
149
+ }
150
+ }
151
+ pos = next;
152
+ }
153
+ if (inFence) ranges.push({
154
+ start: fenceStart,
155
+ end: content.length
156
+ });
157
+ return ranges;
111
158
  }
159
+ function isInRanges(start, end, ranges) {
160
+ for (const range of ranges) if (start < range.end && end > range.start) return true;
161
+ return false;
162
+ }
163
+ function injectIslandMarkers(html, islands) {
164
+ let output = html;
165
+ for (const island of islands) {
166
+ const marker = createIslandMarker(island.id);
167
+ const propsAttr = Object.keys(island.props).length > 0 ? ` data-ox-props='${JSON.stringify(island.props).replace(/'/g, "&#39;")}'` : "";
168
+ const contentAttr = island.content ? ` data-ox-content='${island.content.replace(/'/g, "&#39;")}'` : "";
169
+ const attrs = `data-ox-island="${island.name}"${propsAttr}${contentAttr}`;
170
+ output = output.replaceAll(`<p>${marker}</p>`, `<div ${attrs}></div>`);
171
+ output = output.replaceAll(marker, `<span ${attrs}></span>`);
172
+ }
173
+ return output;
174
+ }
175
+ /**
176
+ * Extracts frontmatter from Markdown content.
177
+ */
112
178
  function extractFrontmatter(content) {
113
- const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
114
- const match = frontmatterRegex.exec(content);
115
- if (!match) {
116
- return { content, frontmatter: {} };
117
- }
118
- const frontmatterStr = match[1];
119
- const frontmatter = {};
120
- for (const line of frontmatterStr.split("\n")) {
121
- const colonIndex = line.indexOf(":");
122
- if (colonIndex > 0) {
123
- const key = line.slice(0, colonIndex).trim();
124
- let value = line.slice(colonIndex + 1).trim();
125
- try {
126
- value = JSON.parse(value);
127
- } catch {
128
- if (typeof value === "string" && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
129
- value = value.slice(1, -1);
130
- }
131
- }
132
- frontmatter[key] = value;
133
- }
134
- }
135
- return {
136
- content: content.slice(match[0].length),
137
- frontmatter
138
- };
179
+ const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
180
+ if (!match) return {
181
+ content,
182
+ frontmatter: {}
183
+ };
184
+ const frontmatterStr = match[1];
185
+ const frontmatter = {};
186
+ for (const line of frontmatterStr.split("\n")) {
187
+ const colonIndex = line.indexOf(":");
188
+ if (colonIndex > 0) {
189
+ const key = line.slice(0, colonIndex).trim();
190
+ let value = line.slice(colonIndex + 1).trim();
191
+ try {
192
+ value = JSON.parse(value);
193
+ } catch {
194
+ if (typeof value === "string" && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
195
+ }
196
+ frontmatter[key] = value;
197
+ }
198
+ }
199
+ return {
200
+ content: content.slice(match[0].length),
201
+ frontmatter
202
+ };
139
203
  }
204
+ /**
205
+ * Parses component props from a string.
206
+ */
140
207
  function parseProps(propsString) {
141
- const props = {};
142
- if (!propsString) return props;
143
- let match;
144
- while ((match = PROP_REGEX.exec(propsString)) !== null) {
145
- const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
146
- if (name) {
147
- if (doubleQuoted !== void 0) {
148
- props[name] = doubleQuoted;
149
- } else if (singleQuoted !== void 0) {
150
- props[name] = singleQuoted;
151
- } else if (braceValue !== void 0) {
152
- try {
153
- props[name] = JSON.parse(`{${braceValue}}`);
154
- } catch {
155
- props[name] = braceValue;
156
- }
157
- } else if (bracketValue !== void 0) {
158
- try {
159
- props[name] = JSON.parse(`[${bracketValue}]`);
160
- } catch {
161
- props[name] = bracketValue;
162
- }
163
- } else {
164
- props[name] = true;
165
- }
166
- }
167
- }
168
- return props;
208
+ const props = {};
209
+ if (!propsString) return props;
210
+ let match;
211
+ while ((match = PROP_REGEX.exec(propsString)) !== null) {
212
+ const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
213
+ if (name) if (doubleQuoted !== void 0) props[name] = doubleQuoted;
214
+ else if (singleQuoted !== void 0) props[name] = singleQuoted;
215
+ else if (braceValue !== void 0) try {
216
+ props[name] = JSON.parse(`{${braceValue}}`);
217
+ } catch {
218
+ props[name] = braceValue;
219
+ }
220
+ else if (bracketValue !== void 0) try {
221
+ props[name] = JSON.parse(`[${bracketValue}]`);
222
+ } catch {
223
+ props[name] = bracketValue;
224
+ }
225
+ else props[name] = true;
226
+ }
227
+ return props;
169
228
  }
170
- function generateVueSFC(content, usedComponents, slots, frontmatter, options, id) {
171
- const mdDir = path.dirname(id);
172
- const root = options.root || process.cwd();
173
- const componentImports = usedComponents.map((name) => {
174
- const componentPath = options.components.get(name);
175
- if (!componentPath) return "";
176
- const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
177
- const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
178
- const importPath = relativePath.startsWith(".") ? relativePath : "./" + relativePath;
179
- return `import ${name} from '${importPath}';`;
180
- }).filter(Boolean).join("\n");
181
- const slotRenderCases = slots.map(
182
- (slot) => `
183
- case '${slot.id}':
184
- return h(${slot.name}, ${JSON.stringify(slot.props)});`
185
- ).join("");
186
- return `
187
- import { h, ref, onMounted, defineComponent } from 'vue';
229
+ /**
230
+ * Generates a Vue component as JavaScript module from the processed Markdown.
231
+ */
232
+ function generateVueSFC(content, usedComponents, islands, frontmatter, options, id) {
233
+ const mdDir = path$1.dirname(id);
234
+ const root = options.root || process.cwd();
235
+ const componentImports = usedComponents.map((name) => {
236
+ const componentPath = options.components.get(name);
237
+ if (!componentPath) return "";
238
+ const absolutePath = path$1.resolve(root, componentPath.replace(/^\.\//, ""));
239
+ const relativePath = path$1.relative(mdDir, absolutePath).replace(/\\/g, "/");
240
+ return `import ${name} from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';`;
241
+ }).filter(Boolean).join("\n");
242
+ const componentMap = usedComponents.map((name) => ` ${name},`).join("\n");
243
+ if (islands.length === 0) return `
244
+ import { h, ref, defineComponent } from 'vue';
245
+
246
+ export const frontmatter = ${JSON.stringify(frontmatter)};
247
+
248
+ const rawHtml = ${JSON.stringify(content)};
249
+
250
+ export default defineComponent({
251
+ name: 'MarkdownContent',
252
+ setup(_, { expose }) {
253
+ expose({ frontmatter });
254
+
255
+ return () =>
256
+ h('div', {
257
+ class: 'ox-content',
258
+ innerHTML: rawHtml,
259
+ });
260
+ },
261
+ });
262
+ `;
263
+ return `
264
+ import { h, ref, onMounted, onBeforeUnmount, defineComponent, render } from 'vue';
265
+ import { initIslands } from '@ox-content/islands';
188
266
  ${componentImports}
189
267
 
190
268
  export const frontmatter = ${JSON.stringify(frontmatter)};
191
269
 
192
270
  const rawHtml = ${JSON.stringify(content)};
193
- const slots = ${JSON.stringify(slots)};
271
+ const components = {
272
+ ${componentMap}
273
+ };
194
274
 
195
- function renderSlot(slotId) {
196
- switch (slotId) {${slotRenderCases}
197
- default:
198
- return null;
199
- }
275
+ function createVueHydrate(container) {
276
+ const mountedTargets = [];
277
+
278
+ return (element, props) => {
279
+ const componentName = element.dataset.oxIsland;
280
+ const Component = components[componentName];
281
+ if (!Component) return;
282
+
283
+ const islandContent = element.dataset.oxContent || element.innerHTML;
284
+ const children = islandContent
285
+ ? { default: () => h('div', { innerHTML: islandContent }) }
286
+ : undefined;
287
+
288
+ const vnode = h(Component, props, children);
289
+ render(vnode, element);
290
+ mountedTargets.push(element);
291
+
292
+ return () => render(null, element);
293
+ };
200
294
  }
201
295
 
202
296
  export default defineComponent({
203
297
  name: 'MarkdownContent',
204
298
  setup(_, { expose }) {
205
- const mounted = ref(false);
299
+ const container = ref(null);
300
+ let controller;
206
301
 
207
302
  onMounted(() => {
208
- mounted.value = true;
303
+ if (container.value) {
304
+ controller = initIslands(createVueHydrate(container.value), {
305
+ selector: '.ox-content [data-ox-island]',
306
+ });
307
+ }
209
308
  });
210
309
 
211
- expose({ frontmatter });
310
+ onBeforeUnmount(() => {
311
+ if (controller) controller.destroy();
312
+ });
212
313
 
213
- return () => {
214
- if (!mounted.value) {
215
- return h('div', {
216
- class: 'ox-content',
217
- innerHTML: rawHtml,
218
- });
219
- }
314
+ expose({ frontmatter });
220
315
 
221
- return h('div', { class: 'ox-content' },
222
- slots.map((slot) => renderSlot(slot.id))
223
- );
224
- };
316
+ return () =>
317
+ h('div', {
318
+ class: 'ox-content',
319
+ ref: container,
320
+ innerHTML: rawHtml,
321
+ });
225
322
  },
226
323
  });
227
324
  `;
228
325
  }
229
326
 
230
- // src/environment.ts
327
+ //#endregion
328
+ //#region src/environment.ts
329
+ /**
330
+ * Creates a Vite environment for Vue markdown processing.
331
+ *
332
+ * @param mode - 'ssr' for server-side rendering, 'client' for client hydration
333
+ * @param options - Resolved Vue integration options
334
+ */
231
335
  function createVueMarkdownEnvironment(mode, options) {
232
- const isSSR = mode === "ssr";
233
- return {
234
- build: {
235
- outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
236
- ssr: isSSR,
237
- rollupOptions: {
238
- input: isSSR ? void 0 : void 0,
239
- output: {
240
- format: isSSR ? "esm" : "esm",
241
- entryFileNames: isSSR ? "[name].js" : "[name].[hash].js",
242
- chunkFileNames: isSSR ? "chunks/[name].js" : "chunks/[name].[hash].js"
243
- }
244
- },
245
- // SSR-specific optimizations
246
- ...isSSR && {
247
- target: "node18",
248
- minify: false
249
- }
250
- },
251
- resolve: {
252
- conditions: isSSR ? ["node", "import"] : ["browser", "import"]
253
- },
254
- optimizeDeps: {
255
- // Pre-bundle Vue for faster cold starts
256
- include: isSSR ? [] : ["vue"],
257
- // Exclude ox-content packages from optimization (they're local)
258
- exclude: ["vite-plugin-ox-content", "vite-plugin-ox-content-vue"]
259
- },
260
- // Development server options (client only)
261
- ...!isSSR && {
262
- dev: {
263
- warmup: ["./src/**/*.vue", "./docs/**/*.md"]
264
- }
265
- }
266
- };
336
+ const isSSR = mode === "ssr";
337
+ return {
338
+ build: {
339
+ outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
340
+ ssr: isSSR,
341
+ rollupOptions: {
342
+ input: isSSR ? void 0 : void 0,
343
+ output: {
344
+ format: isSSR ? "esm" : "esm",
345
+ entryFileNames: isSSR ? "[name].js" : "[name].[hash].js",
346
+ chunkFileNames: isSSR ? "chunks/[name].js" : "chunks/[name].[hash].js"
347
+ }
348
+ },
349
+ ...isSSR && {
350
+ target: "node18",
351
+ minify: false
352
+ }
353
+ },
354
+ resolve: { conditions: isSSR ? ["node", "import"] : ["browser", "import"] },
355
+ optimizeDeps: {
356
+ include: isSSR ? [] : ["vue"],
357
+ exclude: ["@ox-content/vite-plugin", "@ox-content/vite-plugin-vue"]
358
+ },
359
+ ...!isSSR && { dev: { warmup: ["./src/**/*.vue", "./docs/**/*.md"] } }
360
+ };
267
361
  }
268
362
 
269
- // src/index.ts
270
- var import_vite_plugin_ox_content3 = require("vite-plugin-ox-content");
363
+ //#endregion
364
+ //#region src/index.ts
365
+ /**
366
+ * Vite Plugin for Ox Content Vue Integration
367
+ *
368
+ * Uses Vite's Environment API to enable embedding Vue components in Markdown.
369
+ * Provides SSR and client environments for proper hydration.
370
+ */
371
+ /**
372
+ * Creates the Ox Content Vue integration plugin with Environment API support.
373
+ *
374
+ * @example
375
+ * ```ts
376
+ * // vite.config.ts
377
+ * import { defineConfig } from 'vite';
378
+ * import vue from '@vitejs/plugin-vue';
379
+ * import { oxContentVue } from 'vite-plugin-ox-content-vue';
380
+ *
381
+ * export default defineConfig({
382
+ * plugins: [
383
+ * vue(),
384
+ * oxContentVue({
385
+ * srcDir: 'docs',
386
+ * components: {
387
+ * Counter: './src/components/Counter.vue',
388
+ * },
389
+ * }),
390
+ * ],
391
+ * });
392
+ * ```
393
+ */
271
394
  function oxContentVue(options = {}) {
272
- const resolved = resolveVueOptions(options);
273
- let componentMap = /* @__PURE__ */ new Map();
274
- let config;
275
- if (typeof options.components === "object" && !Array.isArray(options.components)) {
276
- componentMap = new Map(Object.entries(options.components));
277
- }
278
- const vueTransformPlugin = {
279
- name: "ox-content:vue-transform",
280
- enforce: "pre",
281
- async configResolved(resolvedConfig) {
282
- config = resolvedConfig;
283
- const componentsOption = options.components;
284
- if (componentsOption) {
285
- const resolvedComponents = await resolveComponentsGlob(
286
- componentsOption,
287
- config.root
288
- );
289
- componentMap = new Map(Object.entries(resolvedComponents));
290
- }
291
- },
292
- async transform(code, id) {
293
- if (!id.endsWith(".md")) {
294
- return null;
295
- }
296
- const result = await transformMarkdownWithVue(code, id, {
297
- ...resolved,
298
- components: componentMap,
299
- root: config.root
300
- });
301
- return {
302
- code: result.code,
303
- map: result.map
304
- };
305
- }
306
- };
307
- const vueEnvironmentPlugin = {
308
- name: "ox-content:vue-environment",
309
- config() {
310
- return {
311
- environments: {
312
- // SSR environment for Vue component rendering
313
- oxcontent_ssr: createVueMarkdownEnvironment("ssr", resolved),
314
- // Client environment for hydration
315
- oxcontent_client: createVueMarkdownEnvironment("client", resolved)
316
- }
317
- };
318
- },
319
- // Environment-specific module resolution
320
- resolveId: {
321
- order: "pre",
322
- async handler(id, _importer, _options) {
323
- if (id === "virtual:ox-content-vue/runtime") {
324
- return "\0virtual:ox-content-vue/runtime";
325
- }
326
- if (id === "virtual:ox-content-vue/components") {
327
- return "\0virtual:ox-content-vue/components";
328
- }
329
- return null;
330
- }
331
- },
332
- load: {
333
- order: "pre",
334
- async handler(id) {
335
- if (id === "\0virtual:ox-content-vue/runtime") {
336
- return generateRuntimeModule(resolved);
337
- }
338
- if (id === "\0virtual:ox-content-vue/components") {
339
- return generateComponentsModule(componentMap);
340
- }
341
- return null;
342
- }
343
- },
344
- // Per-environment build hooks
345
- applyToEnvironment(environment) {
346
- return environment.name === "oxcontent_ssr" || environment.name === "oxcontent_client" || environment.name === "client" || environment.name === "ssr";
347
- }
348
- };
349
- const vueHmrPlugin = {
350
- name: "ox-content:vue-hmr",
351
- apply: "serve",
352
- handleHotUpdate({ file, server, modules }) {
353
- const isComponent = Array.from(componentMap.values()).some(
354
- (path3) => file.endsWith(path3.replace(/^\.\//, ""))
355
- );
356
- if (isComponent) {
357
- const mdModules = Array.from(
358
- server.moduleGraph.idToModuleMap.values()
359
- ).filter((mod) => mod.file?.endsWith(".md"));
360
- if (mdModules.length > 0) {
361
- server.ws.send({
362
- type: "custom",
363
- event: "ox-content:vue-update",
364
- data: { file }
365
- });
366
- return [...modules, ...mdModules];
367
- }
368
- }
369
- return modules;
370
- }
371
- };
372
- const basePlugins = (0, import_vite_plugin_ox_content2.oxContent)(options);
373
- const environmentPlugin = basePlugins.find((p) => p.name === "ox-content:environment");
374
- return [
375
- vueTransformPlugin,
376
- vueEnvironmentPlugin,
377
- vueHmrPlugin,
378
- ...environmentPlugin ? [environmentPlugin] : []
379
- ];
395
+ const resolved = resolveVueOptions(options);
396
+ let componentMap = /* @__PURE__ */ new Map();
397
+ let config;
398
+ if (typeof options.components === "object" && !Array.isArray(options.components)) componentMap = new Map(Object.entries(options.components));
399
+ const vueTransformPlugin = {
400
+ name: "ox-content:vue-transform",
401
+ enforce: "pre",
402
+ async configResolved(resolvedConfig) {
403
+ config = resolvedConfig;
404
+ const componentsOption = options.components;
405
+ if (componentsOption) {
406
+ const resolvedComponents = await resolveComponentsGlob(componentsOption, config.root);
407
+ componentMap = new Map(Object.entries(resolvedComponents));
408
+ }
409
+ },
410
+ async transform(code, id) {
411
+ if (!id.endsWith(".md")) return null;
412
+ const result = await transformMarkdownWithVue(code, id, {
413
+ ...resolved,
414
+ components: componentMap,
415
+ root: config.root
416
+ });
417
+ return {
418
+ code: result.code,
419
+ map: result.map
420
+ };
421
+ }
422
+ };
423
+ const vueEnvironmentPlugin = {
424
+ name: "ox-content:vue-environment",
425
+ config() {
426
+ return { environments: {
427
+ oxcontent_ssr: createVueMarkdownEnvironment("ssr", resolved),
428
+ oxcontent_client: createVueMarkdownEnvironment("client", resolved)
429
+ } };
430
+ },
431
+ resolveId: {
432
+ order: "pre",
433
+ async handler(id, _importer, _options) {
434
+ if (id === "virtual:ox-content-vue/runtime") return "\0virtual:ox-content-vue/runtime";
435
+ if (id === "virtual:ox-content-vue/components") return "\0virtual:ox-content-vue/components";
436
+ return null;
437
+ }
438
+ },
439
+ load: {
440
+ order: "pre",
441
+ async handler(id) {
442
+ if (id === "\0virtual:ox-content-vue/runtime") return generateRuntimeModule(resolved);
443
+ if (id === "\0virtual:ox-content-vue/components") return generateComponentsModule(componentMap);
444
+ return null;
445
+ }
446
+ },
447
+ applyToEnvironment(environment) {
448
+ return environment.name === "oxcontent_ssr" || environment.name === "oxcontent_client" || environment.name === "client" || environment.name === "ssr";
449
+ }
450
+ };
451
+ const vueHmrPlugin = {
452
+ name: "ox-content:vue-hmr",
453
+ apply: "serve",
454
+ handleHotUpdate({ file, server, modules }) {
455
+ if (Array.from(componentMap.values()).some((path) => file.endsWith(path.replace(/^\.\//, "")))) {
456
+ const mdModules = Array.from(server.moduleGraph.idToModuleMap.values()).filter((mod) => mod.file?.endsWith(".md"));
457
+ if (mdModules.length > 0) {
458
+ server.ws.send({
459
+ type: "custom",
460
+ event: "ox-content:vue-update",
461
+ data: { file }
462
+ });
463
+ return [...modules, ...mdModules];
464
+ }
465
+ }
466
+ return modules;
467
+ }
468
+ };
469
+ const environmentPlugin = (0, _ox_content_vite_plugin.oxContent)(options).find((p) => p.name === "ox-content:environment");
470
+ return [
471
+ vueTransformPlugin,
472
+ vueEnvironmentPlugin,
473
+ vueHmrPlugin,
474
+ ...environmentPlugin ? [environmentPlugin] : []
475
+ ];
380
476
  }
477
+ /**
478
+ * Resolves Vue integration options with defaults.
479
+ */
381
480
  function resolveVueOptions(options) {
382
- return {
383
- srcDir: options.srcDir ?? "docs",
384
- outDir: options.outDir ?? "dist",
385
- base: options.base ?? "/",
386
- gfm: options.gfm ?? true,
387
- frontmatter: options.frontmatter ?? true,
388
- toc: options.toc ?? true,
389
- tocMaxDepth: options.tocMaxDepth ?? 3,
390
- components: options.components ?? {},
391
- // Vue-specific options
392
- reactivityTransform: options.reactivityTransform ?? false,
393
- customBlocks: options.customBlocks ?? true
394
- };
481
+ return {
482
+ srcDir: options.srcDir ?? "docs",
483
+ outDir: options.outDir ?? "dist",
484
+ base: options.base ?? "/",
485
+ gfm: options.gfm ?? true,
486
+ frontmatter: options.frontmatter ?? true,
487
+ toc: options.toc ?? true,
488
+ tocMaxDepth: options.tocMaxDepth ?? 3,
489
+ components: options.components ?? {},
490
+ reactivityTransform: options.reactivityTransform ?? false,
491
+ customBlocks: options.customBlocks ?? true
492
+ };
395
493
  }
494
+ /**
495
+ * Generates the runtime module for Vue markdown rendering.
496
+ */
396
497
  function generateRuntimeModule(_options) {
397
- return `
498
+ return `
398
499
  import { h, defineComponent, ref, onMounted } from 'vue';
399
500
 
400
501
  export const OxContentRenderer = defineComponent({
@@ -413,19 +514,19 @@ export const OxContentRenderer = defineComponent({
413
514
  return () => {
414
515
  if (!props.content) return null;
415
516
 
416
- const { html, frontmatter, toc, slots } = props.content;
517
+ const { html, frontmatter, toc, islands } = props.content;
417
518
 
418
- // Render static HTML with component slots
519
+ // Render static HTML with component islands
419
520
  return h('div', {
420
521
  class: 'ox-content',
421
522
  innerHTML: mounted.value ? undefined : html,
422
- }, mounted.value ? renderWithSlots(html, slots, props.components) : undefined);
523
+ }, mounted.value ? renderWithIslands(html, islands, props.components) : undefined);
423
524
  };
424
525
  },
425
526
  });
426
527
 
427
- function renderWithSlots(html, slots, components) {
428
- // Parse and render slots with Vue components
528
+ function renderWithIslands(html, islands, components) {
529
+ // Parse and render islands with Vue components
429
530
  // This is a simplified version - full implementation would use proper parsing
430
531
  return h('div', { innerHTML: html });
431
532
  }
@@ -437,84 +538,86 @@ export function useOxContent() {
437
538
  }
438
539
  `;
439
540
  }
541
+ /**
542
+ * Generates the components registration module.
543
+ */
440
544
  function generateComponentsModule(componentMap) {
441
- const imports = [];
442
- const exports2 = [];
443
- componentMap.forEach((path3, name) => {
444
- imports.push(`import ${name} from '${path3}';`);
445
- exports2.push(` ${name},`);
446
- });
447
- return `
545
+ const imports = [];
546
+ const exports = [];
547
+ componentMap.forEach((path, name) => {
548
+ imports.push(`import ${name} from '${path}';`);
549
+ exports.push(` ${name},`);
550
+ });
551
+ return `
448
552
  ${imports.join("\n")}
449
553
 
450
554
  export const components = {
451
- ${exports2.join("\n")}
555
+ ${exports.join("\n")}
452
556
  };
453
557
 
454
558
  export default components;
455
559
  `;
456
560
  }
561
+ /**
562
+ * Resolves component glob patterns to a component map.
563
+ */
457
564
  async function resolveComponentsGlob(componentsOption, root) {
458
- if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) {
459
- return componentsOption;
460
- }
461
- const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
462
- const result = {};
463
- for (const pattern of patterns) {
464
- const files = await globFiles(pattern, root);
465
- for (const file of files) {
466
- const baseName = path2.basename(file, path2.extname(file));
467
- const componentName = toPascalCase(baseName);
468
- const relativePath = "./" + path2.relative(root, file).replace(/\\/g, "/");
469
- result[componentName] = relativePath;
470
- }
471
- }
472
- return result;
565
+ if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) return componentsOption;
566
+ const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
567
+ const result = {};
568
+ for (const pattern of patterns) {
569
+ const files = await globFiles(pattern, root);
570
+ for (const file of files) {
571
+ const componentName = toPascalCase(path$1.basename(file, path$1.extname(file)));
572
+ result[componentName] = "./" + path$1.relative(root, file).replace(/\\/g, "/");
573
+ }
574
+ }
575
+ return result;
473
576
  }
577
+ /**
578
+ * Simple glob matcher for component files.
579
+ */
474
580
  async function globFiles(pattern, root) {
475
- const files = [];
476
- const isGlob = pattern.includes("*");
477
- if (!isGlob) {
478
- const fullPath = path2.resolve(root, pattern);
479
- if (fs.existsSync(fullPath)) {
480
- files.push(fullPath);
481
- }
482
- return files;
483
- }
484
- const parts = pattern.split("*");
485
- const baseDir = path2.resolve(root, parts[0]);
486
- const ext = parts[1] || "";
487
- if (!fs.existsSync(baseDir)) {
488
- return files;
489
- }
490
- if (pattern.includes("**")) {
491
- await walkDir(baseDir, files, ext);
492
- } else {
493
- const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
494
- for (const entry of entries) {
495
- if (entry.isFile() && entry.name.endsWith(ext)) {
496
- files.push(path2.join(baseDir, entry.name));
497
- }
498
- }
499
- }
500
- return files;
581
+ const files = [];
582
+ if (!pattern.includes("*")) {
583
+ const fullPath = path$1.resolve(root, pattern);
584
+ if (fs.existsSync(fullPath)) files.push(fullPath);
585
+ return files;
586
+ }
587
+ const parts = pattern.split("*");
588
+ const baseDir = path$1.resolve(root, parts[0]);
589
+ const ext = parts[1] || "";
590
+ if (!fs.existsSync(baseDir)) return files;
591
+ if (pattern.includes("**")) await walkDir(baseDir, files, ext);
592
+ else {
593
+ const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
594
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(ext)) files.push(path$1.join(baseDir, entry.name));
595
+ }
596
+ return files;
501
597
  }
598
+ /**
599
+ * Recursively walks a directory.
600
+ */
502
601
  async function walkDir(dir, files, ext) {
503
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
504
- for (const entry of entries) {
505
- const fullPath = path2.join(dir, entry.name);
506
- if (entry.isDirectory()) {
507
- await walkDir(fullPath, files, ext);
508
- } else if (entry.isFile() && entry.name.endsWith(ext)) {
509
- files.push(fullPath);
510
- }
511
- }
602
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
603
+ for (const entry of entries) {
604
+ const fullPath = path$1.join(dir, entry.name);
605
+ if (entry.isDirectory()) await walkDir(fullPath, files, ext);
606
+ else if (entry.isFile() && entry.name.endsWith(ext)) files.push(fullPath);
607
+ }
512
608
  }
609
+ /**
610
+ * Converts a string to PascalCase.
611
+ */
513
612
  function toPascalCase(str) {
514
- return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
613
+ return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
515
614
  }
516
- // Annotate the CommonJS export names for ESM import in node:
517
- 0 && (module.exports = {
518
- oxContent,
519
- oxContentVue
615
+
616
+ //#endregion
617
+ Object.defineProperty(exports, 'oxContent', {
618
+ enumerable: true,
619
+ get: function () {
620
+ return _ox_content_vite_plugin.oxContent;
621
+ }
520
622
  });
623
+ exports.oxContentVue = oxContentVue;