@ox-content/vite-plugin-react 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,352 +1,458 @@
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
- oxContentReact: () => oxContentReact
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 = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}))?/g;
34
+ //#region src/transform.ts
35
+ const COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>([\s\S]*?)<\/\1>)/g;
36
+ const PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
37
+ const ISLAND_MARKER_PREFIX = "OXCONTENT-ISLAND-";
38
+ const ISLAND_MARKER_SUFFIX = "-PLACEHOLDER";
46
39
  async function transformMarkdownWithReact(code, id, options) {
47
- const components = options.components;
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 (componentName in components) {
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
- toc: options.toc,
82
- tocMaxDepth: options.tocMaxDepth,
83
- footnotes: true,
84
- tables: true,
85
- taskLists: true,
86
- strikethrough: true,
87
- highlight: false,
88
- highlightTheme: "github-dark",
89
- mermaid: false,
90
- ogImage: false,
91
- ogImageOptions: {},
92
- transformers: [],
93
- docs: false,
94
- search: { enabled: false, limit: 10, prefix: true, placeholder: "Search...", hotkey: "k" }
95
- });
96
- const jsxCode = generateReactModule(
97
- transformed.html,
98
- usedComponents,
99
- slots,
100
- frontmatter,
101
- options,
102
- id
103
- );
104
- return {
105
- code: jsxCode,
106
- map: null,
107
- usedComponents,
108
- frontmatter
109
- };
40
+ const components = options.components;
41
+ const usedComponents = [];
42
+ const islands = [];
43
+ let islandIndex = 0;
44
+ const { content: markdownContent, frontmatter } = extractFrontmatter(code);
45
+ const fenceRanges = collectFenceRanges(markdownContent);
46
+ let processedContent = "";
47
+ let lastIndex = 0;
48
+ let match;
49
+ COMPONENT_REGEX.lastIndex = 0;
50
+ while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
51
+ const [fullMatch, componentName, propsString, rawIslandContent] = match;
52
+ const matchStart = match.index;
53
+ const matchEnd = matchStart + fullMatch.length;
54
+ if (!Object.prototype.hasOwnProperty.call(components, componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
55
+ processedContent += markdownContent.slice(lastIndex, matchEnd);
56
+ lastIndex = matchEnd;
57
+ continue;
58
+ }
59
+ if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
60
+ const props = parseProps(propsString);
61
+ const islandId = `ox-island-${islandIndex++}`;
62
+ const islandContent = typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0;
63
+ islands.push({
64
+ name: componentName,
65
+ props,
66
+ position: matchStart,
67
+ id: islandId,
68
+ content: islandContent
69
+ });
70
+ processedContent += markdownContent.slice(lastIndex, matchStart) + createIslandMarker(islandId);
71
+ lastIndex = matchEnd;
72
+ }
73
+ processedContent += markdownContent.slice(lastIndex);
74
+ return {
75
+ code: generateReactModule(injectIslandMarkers((await (0, _ox_content_vite_plugin.transformMarkdown)(processedContent, id, {
76
+ srcDir: options.srcDir,
77
+ outDir: options.outDir,
78
+ base: options.base,
79
+ ssg: {
80
+ enabled: false,
81
+ extension: ".html",
82
+ clean: false,
83
+ bare: false,
84
+ generateOgImage: false
85
+ },
86
+ gfm: options.gfm,
87
+ frontmatter: false,
88
+ toc: options.toc,
89
+ tocMaxDepth: options.tocMaxDepth,
90
+ footnotes: true,
91
+ tables: true,
92
+ taskLists: true,
93
+ strikethrough: true,
94
+ highlight: false,
95
+ highlightTheme: "github-dark",
96
+ mermaid: false,
97
+ ogImage: false,
98
+ ogImageOptions: {},
99
+ transformers: [],
100
+ docs: false,
101
+ search: {
102
+ enabled: false,
103
+ limit: 10,
104
+ prefix: true,
105
+ placeholder: "Search...",
106
+ hotkey: "k"
107
+ }
108
+ })).html, islands), usedComponents, islands, frontmatter, options, id),
109
+ map: null,
110
+ usedComponents,
111
+ frontmatter
112
+ };
113
+ }
114
+ function createIslandMarker(islandId) {
115
+ return `${ISLAND_MARKER_PREFIX}${islandId}${ISLAND_MARKER_SUFFIX}`;
116
+ }
117
+ function collectFenceRanges(content) {
118
+ const ranges = [];
119
+ let inFence = false;
120
+ let fenceChar = "";
121
+ let fenceLength = 0;
122
+ let fenceStart = 0;
123
+ let pos = 0;
124
+ while (pos < content.length) {
125
+ const lineEnd = content.indexOf("\n", pos);
126
+ const next = lineEnd === -1 ? content.length : lineEnd + 1;
127
+ const fenceMatch = content.slice(pos, lineEnd === -1 ? content.length : lineEnd).match(/^\s{0,3}([`~]{3,})/);
128
+ if (fenceMatch) {
129
+ const marker = fenceMatch[1];
130
+ if (!inFence) {
131
+ inFence = true;
132
+ fenceChar = marker[0];
133
+ fenceLength = marker.length;
134
+ fenceStart = pos;
135
+ } else if (marker[0] === fenceChar && marker.length >= fenceLength) {
136
+ inFence = false;
137
+ ranges.push({
138
+ start: fenceStart,
139
+ end: next
140
+ });
141
+ fenceChar = "";
142
+ fenceLength = 0;
143
+ }
144
+ }
145
+ pos = next;
146
+ }
147
+ if (inFence) ranges.push({
148
+ start: fenceStart,
149
+ end: content.length
150
+ });
151
+ return ranges;
152
+ }
153
+ function isInRanges(start, end, ranges) {
154
+ for (const range of ranges) if (start < range.end && end > range.start) return true;
155
+ return false;
156
+ }
157
+ function injectIslandMarkers(html, islands) {
158
+ let output = html;
159
+ for (const island of islands) {
160
+ const marker = createIslandMarker(island.id);
161
+ const propsAttr = Object.keys(island.props).length > 0 ? ` data-ox-props='${JSON.stringify(island.props).replace(/'/g, "&#39;")}'` : "";
162
+ const contentAttr = island.content ? ` data-ox-content='${island.content.replace(/'/g, "&#39;")}'` : "";
163
+ const attrs = `data-ox-island="${island.name}"${propsAttr}${contentAttr}`;
164
+ output = output.replaceAll(`<p>${marker}</p>`, `<div ${attrs}></div>`);
165
+ output = output.replaceAll(marker, `<span ${attrs}></span>`);
166
+ }
167
+ return output;
110
168
  }
111
169
  function extractFrontmatter(content) {
112
- const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
113
- const match = frontmatterRegex.exec(content);
114
- if (!match) {
115
- return { content, frontmatter: {} };
116
- }
117
- const frontmatterStr = match[1];
118
- const frontmatter = {};
119
- for (const line of frontmatterStr.split("\n")) {
120
- const colonIndex = line.indexOf(":");
121
- if (colonIndex > 0) {
122
- const key = line.slice(0, colonIndex).trim();
123
- let value = line.slice(colonIndex + 1).trim();
124
- try {
125
- value = JSON.parse(value);
126
- } catch {
127
- if (typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
128
- value = value.slice(1, -1);
129
- }
130
- }
131
- frontmatter[key] = value;
132
- }
133
- }
134
- return { content: content.slice(match[0].length), frontmatter };
170
+ const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
171
+ if (!match) return {
172
+ content,
173
+ frontmatter: {}
174
+ };
175
+ const frontmatterStr = match[1];
176
+ const frontmatter = {};
177
+ for (const line of frontmatterStr.split("\n")) {
178
+ const colonIndex = line.indexOf(":");
179
+ if (colonIndex > 0) {
180
+ const key = line.slice(0, colonIndex).trim();
181
+ let value = line.slice(colonIndex + 1).trim();
182
+ try {
183
+ value = JSON.parse(value);
184
+ } catch {
185
+ if (typeof value === "string" && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
186
+ }
187
+ frontmatter[key] = value;
188
+ }
189
+ }
190
+ return {
191
+ content: content.slice(match[0].length),
192
+ frontmatter
193
+ };
135
194
  }
136
195
  function parseProps(propsString) {
137
- const props = {};
138
- if (!propsString) return props;
139
- let match;
140
- while ((match = PROP_REGEX.exec(propsString)) !== null) {
141
- const [, name, doubleQuoted, singleQuoted, braceValue] = match;
142
- if (name) {
143
- if (doubleQuoted !== void 0) props[name] = doubleQuoted;
144
- else if (singleQuoted !== void 0) props[name] = singleQuoted;
145
- else if (braceValue !== void 0) {
146
- try {
147
- props[name] = JSON.parse(braceValue);
148
- } catch {
149
- props[name] = braceValue;
150
- }
151
- } else props[name] = true;
152
- }
153
- }
154
- return props;
196
+ const props = {};
197
+ if (!propsString) return props;
198
+ PROP_REGEX.lastIndex = 0;
199
+ let match;
200
+ while ((match = PROP_REGEX.exec(propsString)) !== null) {
201
+ const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
202
+ if (name) if (doubleQuoted !== void 0) props[name] = doubleQuoted;
203
+ else if (singleQuoted !== void 0) props[name] = singleQuoted;
204
+ else if (braceValue !== void 0) try {
205
+ props[name] = JSON.parse(braceValue);
206
+ } catch {
207
+ props[name] = braceValue;
208
+ }
209
+ else if (bracketValue !== void 0) try {
210
+ props[name] = JSON.parse(`[${bracketValue}]`);
211
+ } catch {
212
+ props[name] = bracketValue;
213
+ }
214
+ else props[name] = true;
215
+ }
216
+ return props;
155
217
  }
156
- function generateReactModule(content, usedComponents, slots, frontmatter, options, id) {
157
- const mdDir = path.dirname(id);
158
- const root = options.root || process.cwd();
159
- const imports = usedComponents.map((name) => {
160
- const componentPath = options.components[name];
161
- if (!componentPath) return "";
162
- const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
163
- const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
164
- const importPath = relativePath.startsWith(".") ? relativePath : "./" + relativePath;
165
- return `import ${name} from '${importPath}';`;
166
- }).filter(Boolean).join("\n");
167
- return `
168
- import React, { useState, useEffect, createElement } from 'react';
218
+ function generateReactModule(content, usedComponents, islands, frontmatter, options, id) {
219
+ const mdDir = path$1.dirname(id);
220
+ const root = options.root || process.cwd();
221
+ const imports = usedComponents.map((name) => {
222
+ const componentPath = options.components[name];
223
+ if (!componentPath) return "";
224
+ const absolutePath = path$1.resolve(root, componentPath.replace(/^\.\//, ""));
225
+ const relativePath = path$1.relative(mdDir, absolutePath).replace(/\\/g, "/");
226
+ return `import ${name} from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';`;
227
+ }).filter(Boolean).join("\n");
228
+ const componentMap = usedComponents.map((name) => ` ${name},`).join("\n");
229
+ if (islands.length === 0) return `
230
+ import React, { createElement } from 'react';
231
+
232
+ export const frontmatter = ${JSON.stringify(frontmatter)};
233
+
234
+ const rawHtml = ${JSON.stringify(content)};
235
+
236
+ export default function MarkdownContent() {
237
+ return createElement('div', {
238
+ className: 'ox-content',
239
+ dangerouslySetInnerHTML: { __html: rawHtml },
240
+ });
241
+ }
242
+ `;
243
+ return `
244
+ import React, { useEffect, useRef, createElement } from 'react';
245
+ import { createRoot } from 'react-dom/client';
246
+ import { initIslands } from '@ox-content/islands';
169
247
  ${imports}
170
248
 
171
- const frontmatter = ${JSON.stringify(frontmatter)};
249
+ export const frontmatter = ${JSON.stringify(frontmatter)};
250
+
172
251
  const rawHtml = ${JSON.stringify(content)};
173
- const slots = ${JSON.stringify(slots)};
252
+ const components = {
253
+ ${componentMap}
254
+ };
255
+
256
+ function createReactHydrate() {
257
+ const mountedRoots = [];
258
+
259
+ return (element, props) => {
260
+ const componentName = element.dataset.oxIsland;
261
+ const Component = components[componentName];
262
+ if (!Component) return;
174
263
 
175
- const components = { ${usedComponents.join(", ")} };
264
+ const islandContent = element.dataset.oxContent || element.innerHTML;
265
+ const vnode = islandContent
266
+ ? createElement(
267
+ Component,
268
+ props,
269
+ createElement('div', { dangerouslySetInnerHTML: { __html: islandContent } })
270
+ )
271
+ : createElement(Component, props);
272
+
273
+ const root = createRoot(element);
274
+ root.render(vnode);
275
+ mountedRoots.push(root);
276
+
277
+ return () => root.unmount();
278
+ };
279
+ }
176
280
 
177
281
  export default function MarkdownContent() {
178
- const [mounted, setMounted] = useState(false);
282
+ const containerRef = useRef(null);
179
283
 
180
284
  useEffect(() => {
181
- setMounted(true);
182
- }, []);
183
-
184
- if (!mounted) {
185
- return createElement('div', {
186
- className: 'ox-content',
187
- dangerouslySetInnerHTML: { __html: rawHtml }
285
+ if (!containerRef.current) return;
286
+ const controller = initIslands(createReactHydrate(), {
287
+ selector: '.ox-content [data-ox-island]',
188
288
  });
189
- }
289
+ return () => controller.destroy();
290
+ }, []);
190
291
 
191
- return createElement('div', { className: 'ox-content' },
192
- slots.map((slot) => {
193
- const Component = components[slot.name];
194
- return Component ? createElement(Component, { key: slot.id, ...slot.props }) : null;
195
- })
196
- );
292
+ return createElement('div', {
293
+ className: 'ox-content',
294
+ ref: containerRef,
295
+ dangerouslySetInnerHTML: { __html: rawHtml },
296
+ });
197
297
  }
198
-
199
- export { frontmatter };
200
298
  `;
201
299
  }
202
300
 
203
- // src/environment.ts
301
+ //#endregion
302
+ //#region src/environment.ts
204
303
  function createReactMarkdownEnvironment(mode, options) {
205
- const isSSR = mode === "ssr";
206
- return {
207
- build: {
208
- outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
209
- ssr: isSSR,
210
- rollupOptions: {
211
- output: {
212
- format: "esm",
213
- entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
214
- }
215
- },
216
- ...isSSR && { target: "node18", minify: false }
217
- },
218
- resolve: {
219
- conditions: isSSR ? ["node", "import"] : ["browser", "import"]
220
- },
221
- optimizeDeps: {
222
- include: isSSR ? [] : ["react", "react-dom"],
223
- exclude: ["vite-plugin-ox-content", "vite-plugin-ox-content-react"]
224
- }
225
- };
304
+ const isSSR = mode === "ssr";
305
+ return {
306
+ build: {
307
+ outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
308
+ ssr: isSSR,
309
+ rollupOptions: { output: {
310
+ format: "esm",
311
+ entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
312
+ } },
313
+ ...isSSR && {
314
+ target: "node18",
315
+ minify: false
316
+ }
317
+ },
318
+ resolve: { conditions: isSSR ? ["node", "import"] : ["browser", "import"] },
319
+ optimizeDeps: {
320
+ include: isSSR ? [] : ["react", "react-dom"],
321
+ exclude: ["@ox-content/vite-plugin", "@ox-content/vite-plugin-react"]
322
+ }
323
+ };
226
324
  }
227
325
 
228
- // src/index.ts
229
- var import_vite_plugin_ox_content3 = require("vite-plugin-ox-content");
326
+ //#endregion
327
+ //#region src/index.ts
328
+ /**
329
+ * Vite Plugin for Ox Content React Integration
330
+ *
331
+ * Uses Vite's Environment API to enable embedding React components in Markdown.
332
+ */
333
+ /**
334
+ * Creates the Ox Content React integration plugin.
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * // vite.config.ts
339
+ * import { defineConfig } from 'vite';
340
+ * import react from '@vitejs/plugin-react';
341
+ * import { oxContentReact } from 'vite-plugin-ox-content-react';
342
+ *
343
+ * export default defineConfig({
344
+ * plugins: [
345
+ * react(),
346
+ * oxContentReact({
347
+ * srcDir: 'docs',
348
+ * components: {
349
+ * Counter: './src/components/Counter.tsx',
350
+ * },
351
+ * }),
352
+ * ],
353
+ * });
354
+ * ```
355
+ */
230
356
  function oxContentReact(options = {}) {
231
- const resolved = resolveReactOptions(options);
232
- let componentMap = /* @__PURE__ */ new Map();
233
- let config;
234
- if (typeof options.components === "object" && !Array.isArray(options.components)) {
235
- componentMap = new Map(Object.entries(options.components));
236
- }
237
- const reactTransformPlugin = {
238
- name: "ox-content:react-transform",
239
- enforce: "pre",
240
- async configResolved(resolvedConfig) {
241
- config = resolvedConfig;
242
- const componentsOption = options.components;
243
- if (componentsOption) {
244
- const resolvedComponents = await resolveComponentsGlob(
245
- componentsOption,
246
- config.root
247
- );
248
- componentMap = new Map(Object.entries(resolvedComponents));
249
- }
250
- },
251
- async transform(code, id) {
252
- if (!id.endsWith(".md")) {
253
- return null;
254
- }
255
- const result = await transformMarkdownWithReact(code, id, {
256
- ...resolved,
257
- components: Object.fromEntries(componentMap),
258
- root: config.root
259
- });
260
- return {
261
- code: result.code,
262
- map: result.map
263
- };
264
- }
265
- };
266
- const reactEnvironmentPlugin = {
267
- name: "ox-content:react-environment",
268
- config() {
269
- const envOptions = {
270
- ...resolved,
271
- components: Object.fromEntries(componentMap)
272
- };
273
- return {
274
- environments: {
275
- oxcontent_ssr: createReactMarkdownEnvironment("ssr", envOptions),
276
- oxcontent_client: createReactMarkdownEnvironment("client", envOptions)
277
- }
278
- };
279
- },
280
- resolveId(id) {
281
- if (id === "virtual:ox-content-react/runtime") {
282
- return "\0virtual:ox-content-react/runtime";
283
- }
284
- if (id === "virtual:ox-content-react/components") {
285
- return "\0virtual:ox-content-react/components";
286
- }
287
- return null;
288
- },
289
- load(id) {
290
- if (id === "\0virtual:ox-content-react/runtime") {
291
- return generateRuntimeModule();
292
- }
293
- if (id === "\0virtual:ox-content-react/components") {
294
- return generateComponentsModule(componentMap);
295
- }
296
- return null;
297
- },
298
- applyToEnvironment(environment) {
299
- return ["oxcontent_ssr", "oxcontent_client", "client", "ssr"].includes(
300
- environment.name
301
- );
302
- }
303
- };
304
- const reactHmrPlugin = {
305
- name: "ox-content:react-hmr",
306
- apply: "serve",
307
- handleHotUpdate({ file, server, modules }) {
308
- const isComponent = Array.from(componentMap.values()).some(
309
- (path3) => file.endsWith(path3.replace(/^\.\//, ""))
310
- );
311
- if (isComponent) {
312
- const mdModules = Array.from(
313
- server.moduleGraph.idToModuleMap.values()
314
- ).filter((mod) => mod.file?.endsWith(".md"));
315
- if (mdModules.length > 0) {
316
- server.ws.send({
317
- type: "custom",
318
- event: "ox-content:react-update",
319
- data: { file }
320
- });
321
- return [...modules, ...mdModules];
322
- }
323
- }
324
- return modules;
325
- }
326
- };
327
- const basePlugins = (0, import_vite_plugin_ox_content2.oxContent)(options);
328
- const environmentPlugin = basePlugins.find((p) => p.name === "ox-content:environment");
329
- return [
330
- reactTransformPlugin,
331
- reactEnvironmentPlugin,
332
- reactHmrPlugin,
333
- ...environmentPlugin ? [environmentPlugin] : []
334
- ];
357
+ const resolved = resolveReactOptions(options);
358
+ let componentMap = /* @__PURE__ */ new Map();
359
+ let config;
360
+ if (typeof options.components === "object" && !Array.isArray(options.components)) componentMap = new Map(Object.entries(options.components));
361
+ const reactTransformPlugin = {
362
+ name: "ox-content:react-transform",
363
+ enforce: "pre",
364
+ async configResolved(resolvedConfig) {
365
+ config = resolvedConfig;
366
+ const componentsOption = options.components;
367
+ if (componentsOption) {
368
+ const resolvedComponents = await resolveComponentsGlob(componentsOption, config.root);
369
+ componentMap = new Map(Object.entries(resolvedComponents));
370
+ }
371
+ },
372
+ async transform(code, id) {
373
+ if (!id.endsWith(".md")) return null;
374
+ const result = await transformMarkdownWithReact(code, id, {
375
+ ...resolved,
376
+ components: Object.fromEntries(componentMap),
377
+ root: config.root
378
+ });
379
+ return {
380
+ code: result.code,
381
+ map: result.map
382
+ };
383
+ }
384
+ };
385
+ const reactEnvironmentPlugin = {
386
+ name: "ox-content:react-environment",
387
+ config() {
388
+ const envOptions = {
389
+ ...resolved,
390
+ components: Object.fromEntries(componentMap)
391
+ };
392
+ return { environments: {
393
+ oxcontent_ssr: createReactMarkdownEnvironment("ssr", envOptions),
394
+ oxcontent_client: createReactMarkdownEnvironment("client", envOptions)
395
+ } };
396
+ },
397
+ resolveId(id) {
398
+ if (id === "virtual:ox-content-react/runtime") return "\0virtual:ox-content-react/runtime";
399
+ if (id === "virtual:ox-content-react/components") return "\0virtual:ox-content-react/components";
400
+ return null;
401
+ },
402
+ load(id) {
403
+ if (id === "\0virtual:ox-content-react/runtime") return generateRuntimeModule();
404
+ if (id === "\0virtual:ox-content-react/components") return generateComponentsModule(componentMap);
405
+ return null;
406
+ },
407
+ applyToEnvironment(environment) {
408
+ return [
409
+ "oxcontent_ssr",
410
+ "oxcontent_client",
411
+ "client",
412
+ "ssr"
413
+ ].includes(environment.name);
414
+ }
415
+ };
416
+ const reactHmrPlugin = {
417
+ name: "ox-content:react-hmr",
418
+ apply: "serve",
419
+ handleHotUpdate({ file, server, modules }) {
420
+ if (Array.from(componentMap.values()).some((path) => file.endsWith(path.replace(/^\.\//, "")))) {
421
+ const mdModules = Array.from(server.moduleGraph.idToModuleMap.values()).filter((mod) => mod.file?.endsWith(".md"));
422
+ if (mdModules.length > 0) {
423
+ server.ws.send({
424
+ type: "custom",
425
+ event: "ox-content:react-update",
426
+ data: { file }
427
+ });
428
+ return [...modules, ...mdModules];
429
+ }
430
+ }
431
+ return modules;
432
+ }
433
+ };
434
+ const environmentPlugin = (0, _ox_content_vite_plugin.oxContent)(options).find((p) => p.name === "ox-content:environment");
435
+ return [
436
+ reactTransformPlugin,
437
+ reactEnvironmentPlugin,
438
+ reactHmrPlugin,
439
+ ...environmentPlugin ? [environmentPlugin] : []
440
+ ];
335
441
  }
336
442
  function resolveReactOptions(options) {
337
- return {
338
- srcDir: options.srcDir ?? "docs",
339
- outDir: options.outDir ?? "dist",
340
- base: options.base ?? "/",
341
- gfm: options.gfm ?? true,
342
- frontmatter: options.frontmatter ?? true,
343
- toc: options.toc ?? true,
344
- tocMaxDepth: options.tocMaxDepth ?? 3,
345
- jsxRuntime: options.jsxRuntime ?? "automatic"
346
- };
443
+ return {
444
+ srcDir: options.srcDir ?? "docs",
445
+ outDir: options.outDir ?? "dist",
446
+ base: options.base ?? "/",
447
+ gfm: options.gfm ?? true,
448
+ frontmatter: options.frontmatter ?? true,
449
+ toc: options.toc ?? true,
450
+ tocMaxDepth: options.tocMaxDepth ?? 3,
451
+ jsxRuntime: options.jsxRuntime ?? "automatic"
452
+ };
347
453
  }
348
454
  function generateRuntimeModule() {
349
- return `
455
+ return `
350
456
  import React, { useState, useEffect } from 'react';
351
457
 
352
458
  export function OxContentRenderer({ content, components = {} }) {
@@ -358,7 +464,7 @@ export function OxContentRenderer({ content, components = {} }) {
358
464
 
359
465
  if (!content) return null;
360
466
 
361
- const { html, frontmatter, slots } = content;
467
+ const { html, frontmatter, islands } = content;
362
468
 
363
469
  if (!mounted) {
364
470
  return React.createElement('div', {
@@ -368,10 +474,10 @@ export function OxContentRenderer({ content, components = {} }) {
368
474
  }
369
475
 
370
476
  return React.createElement('div', { className: 'ox-content' },
371
- slots.map((slot) => {
372
- const Component = components[slot.name];
477
+ islands.map((island) => {
478
+ const Component = components[island.name];
373
479
  return Component
374
- ? React.createElement(Component, { key: slot.id, ...slot.props })
480
+ ? React.createElement(Component, { key: island.id, ...island.props })
375
481
  : null;
376
482
  })
377
483
  );
@@ -383,83 +489,70 @@ export function useOxContent() {
383
489
  `;
384
490
  }
385
491
  function generateComponentsModule(componentMap) {
386
- const imports = [];
387
- const exports2 = [];
388
- componentMap.forEach((path3, name) => {
389
- imports.push(`import ${name} from '${path3}';`);
390
- exports2.push(` ${name},`);
391
- });
392
- return `
492
+ const imports = [];
493
+ const exports = [];
494
+ componentMap.forEach((path, name) => {
495
+ imports.push(`import ${name} from '${path}';`);
496
+ exports.push(` ${name},`);
497
+ });
498
+ return `
393
499
  ${imports.join("\n")}
394
500
 
395
501
  export const components = {
396
- ${exports2.join("\n")}
502
+ ${exports.join("\n")}
397
503
  };
398
504
 
399
505
  export default components;
400
506
  `;
401
507
  }
402
508
  async function resolveComponentsGlob(componentsOption, root) {
403
- if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) {
404
- return componentsOption;
405
- }
406
- const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
407
- const result = {};
408
- for (const pattern of patterns) {
409
- const files = await globFiles(pattern, root);
410
- for (const file of files) {
411
- const baseName = path2.basename(file, path2.extname(file));
412
- const componentName = toPascalCase(baseName);
413
- const relativePath = "./" + path2.relative(root, file).replace(/\\/g, "/");
414
- result[componentName] = relativePath;
415
- }
416
- }
417
- return result;
509
+ if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) return componentsOption;
510
+ const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
511
+ const result = {};
512
+ for (const pattern of patterns) {
513
+ const files = await globFiles(pattern, root);
514
+ for (const file of files) {
515
+ const componentName = toPascalCase(path$1.basename(file, path$1.extname(file)));
516
+ result[componentName] = "./" + path$1.relative(root, file).replace(/\\/g, "/");
517
+ }
518
+ }
519
+ return result;
418
520
  }
419
521
  async function globFiles(pattern, root) {
420
- const files = [];
421
- const isGlob = pattern.includes("*");
422
- if (!isGlob) {
423
- const fullPath = path2.resolve(root, pattern);
424
- if (fs.existsSync(fullPath)) {
425
- files.push(fullPath);
426
- }
427
- return files;
428
- }
429
- const parts = pattern.split("*");
430
- const baseDir = path2.resolve(root, parts[0]);
431
- const ext = parts[1] || "";
432
- if (!fs.existsSync(baseDir)) {
433
- return files;
434
- }
435
- if (pattern.includes("**")) {
436
- await walkDir(baseDir, files, ext);
437
- } else {
438
- const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
439
- for (const entry of entries) {
440
- if (entry.isFile() && entry.name.endsWith(ext)) {
441
- files.push(path2.join(baseDir, entry.name));
442
- }
443
- }
444
- }
445
- return files;
522
+ const files = [];
523
+ if (!pattern.includes("*")) {
524
+ const fullPath = path$1.resolve(root, pattern);
525
+ if (fs.existsSync(fullPath)) files.push(fullPath);
526
+ return files;
527
+ }
528
+ const parts = pattern.split("*");
529
+ const baseDir = path$1.resolve(root, parts[0]);
530
+ const ext = parts[1] || "";
531
+ if (!fs.existsSync(baseDir)) return files;
532
+ if (pattern.includes("**")) await walkDir(baseDir, files, ext);
533
+ else {
534
+ const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
535
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(ext)) files.push(path$1.join(baseDir, entry.name));
536
+ }
537
+ return files;
446
538
  }
447
539
  async function walkDir(dir, files, ext) {
448
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
449
- for (const entry of entries) {
450
- const fullPath = path2.join(dir, entry.name);
451
- if (entry.isDirectory()) {
452
- await walkDir(fullPath, files, ext);
453
- } else if (entry.isFile() && entry.name.endsWith(ext)) {
454
- files.push(fullPath);
455
- }
456
- }
540
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
541
+ for (const entry of entries) {
542
+ const fullPath = path$1.join(dir, entry.name);
543
+ if (entry.isDirectory()) await walkDir(fullPath, files, ext);
544
+ else if (entry.isFile() && entry.name.endsWith(ext)) files.push(fullPath);
545
+ }
457
546
  }
458
547
  function toPascalCase(str) {
459
- return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
548
+ return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
460
549
  }
461
- // Annotate the CommonJS export names for ESM import in node:
462
- 0 && (module.exports = {
463
- oxContent,
464
- oxContentReact
550
+
551
+ //#endregion
552
+ Object.defineProperty(exports, 'oxContent', {
553
+ enumerable: true,
554
+ get: function () {
555
+ return _ox_content_vite_plugin.oxContent;
556
+ }
465
557
  });
558
+ exports.oxContentReact = oxContentReact;